---
title: SAIF.NetCore.SystemHash security and authorization
summary: Machine/application caller-identity credential scheme, enforcement points, and security findings.
status: draft
owner: SAIF Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.SystemHash
reference_type: package
---

# SAIF.NetCore.SystemHash security and authorization

This library implements no user or AD-group authorization. It is the shared client/validator toolkit for a **machine/application (caller-identity) authorization scheme**: an application presents a *claimed* identity via a salted, time-boxed credential derived from its own assembly name and a `SystemType` classification. The credential is not proof of possession — it is derivable by anyone who knows the target's assembly name and `SystemType` (see SEC-SH-F01) — and this library's own lifetime check (EP-SH-01) verifies only that the credential is fresh. The final accept/reject decision, including comparing the recovered identity against an authorized value, is made by an external validator (the Environment API / CryptoAPI), which is out of this repository's scope.

## Authorization architecture

The following diagram traces the end-to-end flow across the three distinct roles: this library issues a salted credential and locally checks only its freshness, while the external validator (Environment API / CryptoAPI) performs the final identity comparison and accept/reject decision.

```mermaid
flowchart TD
    Assembly[Resolve system name from assembly] --> Identity["GetHashValue: SHA-256/Base64 of systemName-ordinal"]
    Identity --> Salt["SaltShaker.SaltHash with DateTime.Now"]
    Salt --> Credential["Salted credential, regenerated<br>and randomized per call"]
    Credential --> Transport["systemHashValue query parameter"]
    Transport --> Validator["(External: Environment API / CryptoAPI)"]
    Validator --> Desalt["RemoveSystemHashSaltAndValidateLifetime:<br>de-salt and check window"]
    Desalt --> Expired(["Reject: SystemHashExpiredException"])
    Desalt --> Compare["Compare returned hash to authorized/<br>ComputeHashNow value (external)"]
    Compare --> Accept([Accept])
    Compare --> Reject([Reject])
```

## Identity catalog

This library's "resources" are **system identities**, not AD groups. An identity is the string `"{systemName}-{(int)SystemType}"`; its SHA-256/Base64 digest is the canonical identity hash ([Compute the identity hash](operations/compute-identity-hash.md)). No AD group, LDAP string, or `SAMAccountName`/`$`-suffix machine-account handling exists anywhere in this library.

| Identity string (pre-hash) | `SystemType` (ordinal) | Produced by | How `systemName` is resolved | Source |
| --- | --- | --- | --- | --- |
| `"{Startup assembly simple name}-{n}"` | Caller-chosen | `SystemHashService(Assembly.GetAssembly(type)).Get(type)` registered by `RegisterSecureEnvironmentClient` | Injected assembly = assembly that contains the startup `Type` | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` L19, L61–65; `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs` |
| `"SAIF.NetCore.DBConnectionClient-2"` | `WebApp` (2) | `new SystemHashService(typeof(ConnectionClient).Assembly).Get(SystemType.WebApp)` | Injected assembly = ConnectionClient assembly (`AssemblyName` = `SAIF.NetCore.DBConnectionClient` per its csproj) | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` L19, L61–65; `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs` |
| `"SAIF.NetCore.OIDC-6"` | `Other` (6) | `_systemHashService.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name)` inside `OidcSettings` | `GetExecutingAssembly()` is always the OIDC library assembly; the simple name shown here is inferred from the OIDC project's assembly name, not read directly from a build artifact.* | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` L26–32; `src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs` L32 |
| `"SAIF.NetCore.SystemHash-{n}"` (probable) | Any | `new SystemHashService().Get(type)` (no injected assembly) | `Assembly.GetCallingAssembly()` evaluated inside `GetAssembly`, which `Get` calls, so it most likely resolves to the SystemHash assembly itself ([BR-SH-06](business-rules.md#br-sh-06-system-name-from-assembly)), unless JIT inlining collapses the call stack.* | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` L61–65, L72–75 |

`SystemType` ordinals (`SAIF.NetCore.ServiceContracts`): `Console`=0, `WindowsService`=1, `WebApp`=2, `Guidewire`=3, `ThickClient`=4, `Script`=5, `Other`=6.

## Authorization enforcement points

### Attribute-based authorization

**None.** The library has no attribute, controller, or action of any kind.

### Programmatic authorization checks

| ID | Enforcement point | Kind | What it checks | Outcome on failure | Source |
| --- | --- | --- | --- | --- | --- |
| EP-SH-01 | `SystemHashService.RemoveSystemHashSaltAndValidateLifetime(systemHashValue, expireSeconds, nowBufferSeconds)` | Credential lifetime gate (validator side) | De-salts the credential, recovers the embedded `createDate`, and accepts only if `createDate ≤ now + abs(nowBufferSeconds)` AND `now − abs(nowBufferSeconds) < createDate + expireSeconds` ([BR-SH-08](business-rules.md#br-sh-08-credential-lifetime-window)); this simplified predicate holds only for non-overflowing `int` arithmetic and in-range `DateTime` values — see [BR-SH-08](business-rules.md#br-sh-08-credential-lifetime-window) for the literal source arithmetic and its failure modes | `throw new SystemHashExpiredException(now, createDate)` (type from `SAIF.NetCore.HashUtil`); or an uncaught `ArgumentOutOfRangeException` from the date arithmetic itself | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` L85–100 |

EP-SH-01 is documented step-by-step in [De-salt and validate credential lifetime](operations/validate-credential-lifetime.md). There are **no in-repo production callers** of EP-SH-01 — only this library's own unit tests exercise it; the documented intended caller is the external Environment API / CryptoAPI.

### Supporting (non-enforcing) credential operations

| ID | Member | Role in scheme |
| --- | --- | --- |
| SP-SH-01 | `ComputeHash(SystemType, string)` | Issues a credential: identity hash salted with `DateTime.Now`, regenerated and randomized on every call (not guaranteed unique). See [Compute a salted system hash](operations/compute-salted-system-hash.md). |
| SP-SH-02 | `Get(SystemType)` | Issues a credential for the resolved assembly identity; no caching despite the XML doc. See [Get the current application's system hash](operations/get-current-application-hash.md). |
| SP-SH-03 | `GetAssembly()` | Resolves the identity name. See [Resolve the identity assembly](operations/resolve-identity-assembly.md). |
| SP-SH-04 | `ComputeHashNow(SystemType, string)` | Produces the canonical (unsalted) identity hash a validator compares against EP-SH-01 output. See [Compute an unsalted system hash on demand](operations/compute-unsalted-system-hash.md). |
| SP-SH-05 | `GetHashValue` (private) | SHA-256 over UTF-16LE `"{systemName}-{(int)type}"`, Base64, unkeyed. See [Compute the identity hash](operations/compute-identity-hash.md). |

## Data-level authorization

**None.** This library performs no entity-ownership or scope checks; it issues and validates a caller-identity credential only.

## Segregation of duties

**None.** No SOD rules exist in this library.

## Security interfaces and contracts

| Interface / Class | Role | Consumers |
| --- | --- | --- |
| `ISystemHashService` (`SAIF.NetCore.ServiceContracts`) | Credential issuance and lifetime-validation contract | `SystemHashService` (sole implementation); `BaseEnvironmentClient`, `OidcSettings` (external consumers) |
| `SystemHashExpiredException` (`SAIF.NetCore.HashUtil`) | Signals an expired or not-yet-valid credential | `SystemHashService.RemoveSystemHashSaltAndValidateLifetime` |

### Machine/application authorization

This library implements **application/machine-identity authorization by bearer credential**, not the AD machine-account pattern — there is no `SAMAccountName`, `$`-suffix handling, or `MachineSecurityService` lookup anywhere in this repository.

| Element | Implementation |
| --- | --- |
| Identity basis | `SystemType` + system name (assembly simple name or caller-supplied string) |
| Credential issuance | `ComputeHash`/`Get` → SHA-256 identity hash, salted by `SaltShaker(DateTime.Now)` with an embedded timestamp |
| Credential transport | `systemHashValue` query parameter (consumer call sites) |
| Lifetime validation | `RemoveSystemHashSaltAndValidateLifetime(value, expireSeconds, nowBufferSeconds)` → `SystemHashExpiredException` |
| Canonical identity for comparison | `ComputeHashNow` |
| Authorized-system list comparison | **Not in this library** — unknown, external (Environment API / CryptoAPI) |
| Relationship to user authorization | Independent; authorizes the **calling application**, not a person |

Credential flow:

```text
Issuer (consumer app)                                   Validator (external Environment API / CryptoAPI)
---------------------                                   ------------------------------------------------
GetAssembly() -> name ┐
                      ├─ GetHashValue: SHA256(UTF16("{name}-{ordinal}")) -> Base64 (44)
SystemType ───────────┘
SaltShaker(DateTime.Now).SaltHash -> salted credential (~120 chars, regenerated/randomized per call)
        │  systemHashValue query param
        └──────────────────────────────────────────────►  RemoveSystemHashSaltAndValidateLifetime(v, expire, buffer)
                                                            ├─ expired/not-yet-valid -> SystemHashExpiredException
                                                            └─ returns unsalted hash -> compare to authorized hash
                                                               (ComputeHashNow / stored list — not in this repo)
```

## Security findings and risks

Severity scale: **High** (realistic path to acting with another system's authority), **Medium** (weakens a control or enables identity confusion under plausible conditions), **Low** (fails closed / limited impact), **Informational**.

| # | Finding | Severity | Source |
| --- | --- | --- | --- |
| SEC-SH-F01 | Unkeyed, publicly derivable credential: the identity hash is SHA-256 of a public string (assembly name plus enum ordinal), with no secret, key, HMAC, or signature. Anyone who knows the target's assembly name and `SystemType` can compute the identity hash and, using the public `SaltShaker`, mint a credential that passes EP-SH-01. Scheme strength depends entirely on validator/network-level controls.* | High | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` |
| SEC-SH-F02 | Identity collision when `Get()` runs without an injected assembly: `GetAssembly()` returns `Assembly.GetCallingAssembly()`, evaluated with `Get` as the caller, so the identity is probably `"SAIF.NetCore.SystemHash-{n}"` for every application that uses `new SystemHashService()` directly. JIT inlining could change this between Debug/Release. Production DI always injects an assembly, so exposure is limited to direct `new SystemHashService()` usage.* | Medium | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` |
| SEC-SH-F03 | Shared identity via OIDC consumer: `OidcSettings` passes `Assembly.GetExecutingAssembly().GetName().Name`, which inside `OidcSettings` always resolves to the OIDC library assembly — every application using `SAIF.NetCore.OIDC` presents the same identity when retrieving app credentials. Per-application isolation then depends on the secure-setting lookup path rather than on the hash.* | Medium | `src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs` |
| SEC-SH-F04 | Null `ISystemHashService` registration in a sibling library: `RegisterEnvironmentClient` registers `services.AddSingleton<ISystemHashService>((provider) => { return null; })` for non-secure startups, so `BaseEnvironmentClient`'s `SystemType` overloads throw `NullReferenceException` instead of presenting a credential. Fails closed (no credential sent) but the failure is unhandled. | Medium (availability) | `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs` |
| SEC-SH-F05 | Replayable or re-datable credential within the window: the credential has no nonce or audience, and the embedded timestamp sits at salt-map-disclosed positions. A captured credential can be replayed throughout its full `expireSeconds + abs(nowBufferSeconds)` lifetime from issuance — of which only the final `abs(nowBufferSeconds)` is grace past its nominal `createDate + expireSeconds` expiry — or re-salted with a newer time, defeating the lifetime limit ([BR-SH-08](business-rules.md#br-sh-08-credential-lifetime-window)). Exposure is higher because the credential travels in a query string, which is often logged.* | Medium | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs`, `src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs` |
| SEC-SH-F06 | Lifetime check depends on time zone and culture: issuer and validator both use host-local `DateTime.Now`, and HashUtil formats/parses the embedded date with the current culture. If hosts differ in time zone, credentials can be rejected incorrectly or accepted for too long.* | Low–Medium | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` |
| SEC-SH-F07 | Malformed credentials are not normalized: tampered or garbage input produces BCL exceptions (`ArgumentOutOfRangeException`, `FormatException`, `NullReferenceException`) rather than `SystemHashExpiredException` ([BR-SH-09](business-rules.md#br-sh-09-malformed-credential-raises-bcl-exceptions)). A validator that catches only `SystemHashExpiredException` could return a 500 or leak a stack trace instead of a clean authorization failure.* | Low | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` |
| SEC-SH-F08 | Validation does not establish identity: EP-SH-01 checks freshness only ([BR-SH-10](business-rules.md#br-sh-10-lifetime-validation-is-not-identity)). If a validator stops after EP-SH-01 and skips comparing the returned hash, any well-formed, fresh credential passes. | Informational (design) | `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs` |
| SEC-SH-F09 | Identity depends on enum ordinals: reordering or inserting `SystemType` members in `ServiceContracts` silently changes every identity hash for later members, which would break authorization for already-registered systems ([BR-SH-02](business-rules.md#br-sh-02-identity-string-uses-type-ordinal)).* | Low | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/Enums.cs` |

## Notes
- The external validator's `expireSeconds`/`nowBufferSeconds` values, the authorized-system comparison, and transport protections are not determinable from this repository.**

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [API reference](api/index.md)

> **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.

## Sources
- `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs`
- `src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash.Tests/SystemHashTests.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISystemHashService.cs`, `Enums.cs`
- `src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs`, `SystemHashExpiredException.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs`
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`
- `src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs`
