SAIF.NetCore.EnvironmentClient security and authorization¶
This library performs machine authorization, not end-user authorization: it decides whether the computer it runs on may use developer-only configuration shortcuts, and it authorizes the calling application to the Environment API with a system hash for secure (crypto) setting retrieval. There is no centralized WCF SecurityService integration; the AD check is performed locally through System.DirectoryServices.AccountManagement.
Authorization is local machine check + remote per-secret authorization. AD computer-group membership is checked locally by this library; secret access is authorized by the Environment API (outside this repository) using a system hash this library presents as a query parameter.
flowchart TD
Ctor["Client constructor<br>BaseEnvironmentClient..ctor → Setup()"] --> Resolve["HostResolver.ResolveHost"]
Resolve --> EP1{"EP-1: override allowed<br>and machine in Developer_Machine_Users?"}
EP1 -- "yes + ApplicationHost set" --> Dev["AppConfigOverride = true<br>(IsDeveloperMachine)"]
EP1 -- "no" --> Path["Host from content path<br>or machine name"]
Dev --> EP3["EP-3: developer override file enabled"]
Dev --> EP4
Path --> EP4{"EP-4 (SecureEnvironmentClient only):<br>COREAPIENABLED=1 or developer machine?"}
EP4 -- "no" --> Deny1["EnvironmentClientException<br>not enabled for CORE APIs"]
EP4 -- "yes" --> Call["GetSecureSetting*"]
Call --> Cache{"DL-1: cached by<br>section.key?"}
Cache -- "hit" --> Return["Return cached secret<br>(hash not rechecked)"]
Cache -- "miss or stale" --> EP6["EP-6: GET /v2/secureSetting/...<br>?systemHashValue=hash"]
EP6 --> Server["Environment API validates hash<br>(outside repository)"]
Base["BaseEnvironmentClient<br>GetSecureSetting*"] --> EP5["EP-5: always throws<br>Operation not allowed"]
One AD group is referenced. It is a computer group, not a user resource, and does not follow the SAIF <ResourceName>Resource/<ResourceName>TestResource naming convention, so no environment suffix is applied.
Resource name
Context
Enforcement point
Developer_Machine_Users
AD computer group. Membership of the running machine's computer account marks the machine as a developer workstation, permitting the AppSettings:ApplicationHost host/track override, the local developer settings override file, and construction of SecureEnvironmentClient without COREAPIENABLED=1.
The public contract implemented by BaseEnvironmentClient/SecureEnvironmentClient. Exposes IsDeveloperMachine() (EP-2) and the full family of GetSecureSetting*/GetSecureSettingAsync* overloads (both systemHashValue-based and SystemType-based) that route through EP-5/EP-6, plus the non-secure GetSettingValue*/GetSectionSettings/HasSetting members.
ISystemHashService
SAIF.NetCore.Services.Contracts
Injected dependency used only by the SystemType-based GetSecureSetting* overloads. Get(SystemType type) returns the calling application's hash. RegisterEnvironmentClient supplies this as null; RegisterSecureEnvironmentClient supplies a real SystemHashService. The interface also exposes ComputeHash, ComputeHashNow, GetAssembly, and RemoveSystemHashSaltAndValidateLifetime, none of which this library calls directly.
SystemType (enum)
SAIF.NetCore.Services.Contracts
Classifies the calling application (Console, WindowsService, WebApp, Guidewire, ThickClient, Script, Other) for hash generation via ISystemHashService.Get. This library does not interpret the enum value itself; it is opaque input to the hash service.
The caller supplies, or ISystemHashService.Get(SystemType) computes, a systemHashValue sent as a query parameter to GET /v2/secureSetting/{app}/{track}[/{section}]/{key}/value; the Environment API decides whether the hash authorizes the secret. The client also requires the key to exist in the non-secure settings list (HasSetting) before calling
Non-success HTTP result → EnvironmentClientException (404 → "could not find a value", other → "Failed to retrieve secure setting"), or the transport exception is rethrown
Opaque hash identifying the calling application, produced by ISystemHashService.Get(SystemType) or passed in by the caller. For consumers using RegisterSecureEnvironmentClient(type), the service is constructed from the consumer's entry assembly
There is no entity-ownership check in this library. Per-secret authorization is delegated to the Environment API through the system hash (EP-6). One client-side behavior weakens that per-secret model.
Finding
Severity
The secure-setting cache ignores the system hash.SecureSettingCache is keyed by "{section}.{configKey}" only. After one call fetches a secret with hash A, any later call in the same process for the same section and key is served from the cache, whatever hash it passes (including an invalid or empty one), until LastRefreshed advances. The server is not asked to re-authorize the second caller. Because the client is registered as a singleton per application, this matters only when code inside one application passes different hashes; it does not cross application boundaries*
Medium
The secure fetch is pre-filtered by HasSetting(configKey, section), which consults the non-secure settings dictionary for the host. If the key is not listed there, no request is made and the caller gets "No crypto data found". The secret's existence is therefore disclosed by, and dependent on, the non-secure settings feed.
Computer account named by Environment.MachineName (through ISystemWrapper.GetComputerName)
ComputerPrincipal.IsMemberOf(GroupPrincipal "Developer_Machine_Users"); does not strip a $ suffix
Host/track override (EP-1), developer override file (EP-3), and bypass of COREAPIENABLED (EP-4)
Server enablement flag
Configuration of the host
IConfiguration["COREAPIENABLED"] == "1". XML doc and comment describe this as the ASPNETCORE_COREAPIENABLED environment variable, but the code reads IConfiguration, so any configuration source can satisfy it
Construction of SecureEnvironmentClient (EP-4)
Application identity
Consuming application
systemHashValue (EP-6), and IHttpJsonClient.SetClientInfo on every request
Secure-setting cache is not partitioned by system hash (see Data-level authorization findings)*.
The system hash travels in the URL query string, so it can be recorded in proxy, web-server, and HTTP-client logs, and in any trace that captures the request URL*.
EP-4 runs after the base constructor has already called Setup(), which contacts the Environment API and downloads the track list from a non-enabled server before the gate throws.
The COREAPIENABLED gate reads IConfiguration, not only an environment variable, so an appsettings entry is sufficient to enable secret retrieval; the constructor comment ("or you must be debugging in visual studio to have the environment variable VisualStudioDir") does not match the code, which uses IsDeveloperMachine().
A machine in Developer_Machine_Users can point itself at any track by setting AppSettings:ApplicationHost, and can replace any non-secure setting (for example service URLs) through the override file; secure settings are not overridable by the file, and their release still depends on the server-side hash check.
EnvApiDeveloperOverrideAllowed defaults to allowed when absent, so production servers rely on not being members of Developer_Machine_Users rather than on an explicit opt-out.
Plaintext secrets are held for the process lifetime in SecureSettingCacheItem.Value with no eviction other than ClearCache() or refresh, and ClearCache() is never invoked by this library.
Secure-setting telemetry spans tag settings.section and settings.key (not the value or hash), so secret key names reach the tracing backend.
With RegisterEnvironmentClient, the SystemType overloads throw NullReferenceException (null ISystemHashService) instead of the intended EP-5 "Operation not allowed!" message; the call is still denied*.
How does the Environment API validate systemHashValue (lifetime, salt, per-application binding, per-secret ACL)? The server is not in this repository.**
Which headers and credentials does IHttpJsonClient.SetClientInfo send? The implementation is supplied by the consumer.**
Which domain does PrincipalContext(ContextType.Domain) bind to on a non-domain or local service account, and does the lookup work on non-Windows hosts (net8.0/net10.0 targets)?**
Who governs membership of Developer_Machine_Users, and is it guaranteed that no server computer account is a member?**
Note: Statements marked with * were inferred from code structure, naming
conventions, or contextual evidence where direct confirmation was not available
in source artifacts. Statements marked with ** could not be confirmed; treat
as unknown until verified.