ShieldySdk 1.0.1

dotnet add package ShieldySdk --version 1.0.1
                    
NuGet\Install-Package ShieldySdk -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ShieldySdk" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ShieldySdk" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="ShieldySdk" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ShieldySdk --version 1.0.1
                    
#r "nuget: ShieldySdk, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ShieldySdk@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ShieldySdk&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=ShieldySdk&version=1.0.1
                    
Install as a Cake Tool

Shieldy .NET SDK

The .NET client for Shieldy's native runtime (native.dll on Windows, native.so on Linux). It signs your application in with the end user's license key and reads what the dashboard delivers to that license: the license itself, remote variables, files and Protected Strings.

using ShieldySdk;

try
{
    Shieldy.Start(new ShieldyConfig("<App GUID>", "1.0"), ReadLicenseKeyFromUser());
}
catch (ShieldyException e)
{
    // e.Error is a ShieldyError, e.g. ShieldyError.AuthenticationFailed
    ShowError("Could not sign in: " + e.Error);
    return;
}

ShieldyLicense license = Shieldy.GetLicense();
if (license.Level >= 20) EnableProFeatures();

string apiUrl = Shieldy.GetVariableById("<Lookup ID>");              // string
int maxUsers = Shieldy.GetVariableById<int>("<Lookup ID>");           // int, long, double, bool
using (SecureBuffer pak = Shieldy.GetFileById("<Lookup ID>"))         // wiped when disposed
{
    LoadAssets(pak.OpenRead());
}
string token = Shieldy.GetProtectedString("<ciphertext>");           // string

One package, ShieldySdk, for .NET Framework 4.6.2 and later, every .NET from 8 up, and .NET Standard 2.0. The only dependency is Bouncy Castle (BouncyCastle.Cryptography, MIT).

Installing

dotnet add package ShieldySdk              # once published
dotnet add reference path\to\ShieldyDotnetSdk\src\ShieldySdk\ShieldySdk.csproj   # from a checkout

Ship the runtime with your application. Download it from the dashboard (your app → Activity → Runtime → "Download the SDK") and put it next to your executable, for example:

<ItemGroup>
  <None Include="native\native.dll" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Compatibility

Your application Works The package gives it
.NET Framework 4.6.2, 4.7.x, 4.8, 4.8.1 (WinForms, WPF, console, services) yes lib/net462
.NET 8, 9, 10 and later yes lib/net8.0 (trimming and Native AOT annotated)
.NET Core 3.1, .NET 5, 6, 7 yes, not tested (out of Microsoft support) lib/netstandard2.0
Mono, Unity (Mono backend), other .NET Standard 2.0 hosts best effort, not tested lib/netstandard2.0
.NET Framework 4.0 - 4.6.1 no out of Microsoft support since 2022; retarget to 4.6.2 (usually one line, the app keeps running on the same machines)
Toolchain Works
Visual Studio 2017 or later, MSBuild 15+, any .NET SDK yes
PackageReference and packages.config yes
C# 7.3 and later in your project yes (the SHIELDY_OBF file is written in C# 3)
Processor Ship
x64 process the 64-bit runtime
x86 process the 32-bit runtime

Match the runtime's bitness to the process. Old WinForms and WPF projects are often "AnyCPU" with "Prefer 32-bit" on, which makes the process 32-bit even on 64-bit Windows. Loading a 64-bit native.dll into it fails with NativeLibraryNotFound and a message that says so; set <PlatformTarget>x64</PlatformTarget> (or turn "Prefer 32-bit" off), or ship the 32-bit runtime.

Where the runtime is loaded from

The SDK loads exactly one file, chosen in this order:

  1. ShieldyConfig.NativeLibrary, if you set it;
  2. the SHIELDY_NATIVE_LIBRARY environment variable;
  3. native.dll (native.so on Linux).

A relative path is resolved against your application's directory (AppContext.BaseDirectory), never the current working directory or PATH, so a library planted where the user starts your program is not picked up. On Windows the runtime's own dependencies are searched only next to it and in System32. Shieldy.ApplicationDirectory and Shieldy.ResolveNativeLibrary() show what the SDK settled on.

  • Single-file publish (PublishSingleFile): keep native.dll next to the executable, not inside the bundle (<ExcludeFromSingleFile>true</ExcludeFromSingleFile> on its item).
  • Trimming and Native AOT: the net8.0 build is marked trimmable and AOT-compatible and is clean under the trim and AOT analyzers: the runtime is called through unmanaged function pointers, with no reflection or runtime-generated marshalling. A Native AOT publish itself has not been tested yet.

API

Every call is a static method on Shieldy, the one session of the process. All are thread-safe.

Call Returns
Init(config) Loads the runtime and runs its protected start-up (/initialize on the server).
Login(key) Signs in with the license key.
Start(config, key) Init then Login.
TryInit, TryLogin, TryStart The same, with out ShieldyError instead of an exception.
Shutdown() Ends the session. Also runs when the process exits.
IsLoggedIn bool
Supports(ShieldyFeature.LookupIds) Whether the runtime has the *ById calls (protocol 1.6.0+).
Supports(ShieldyFeature.OfflineStart) Whether the runtime has GetConnectionState() (protocol 1.12.0+).
GetConnectionState() ShieldyConnectionState: IsOffline (this run started without a connection), OfflineUntil (until when the device can start offline; null: it cannot).
GetLicense() ShieldyLicense: Level, HwidSeatsUsed, HwidSeatsTotal, Created, Expires (null: never), IsExpired, DaysLeft. Read once at login.
GetVariableById(id) / GetVariable(name) string
GetVariableById<T>(id) / GetVariable<T>(name) string, bool, int, long or double
GetFileById(id) / GetFile(name) SecureBuffer, wiped when disposed
GetProtectedString(ciphertext) string, cached for the rest of the session

Prefer the *ById calls: a lookup id is random, a name ends up readable in your assembly. A variable whose name is hidden from the client (dashboard: "Hide name from client") can only be read by id.

bool variables accept true, false, 1, 0. Numbers follow the same rules in every Shieldy SDK: surrounding whitespace is ignored; a leading +, digit separators, hex and culture formats (1,5) are not accepted. The current culture never matters.

Errors

Every call throws ShieldyException when it cannot deliver; e.Error says why, and for a runtime that cannot be loaded e.Message says where it looked. The numbers are the same in the C++ and Python SDKs.

ShieldyError Meaning
NativeLibraryNotFound No runtime at the resolved path, or one for the other bitness.
NativeLibraryInvalid The file loaded but is not a Shieldy runtime.
InitializationFailed The server refused to start the app: unknown App GUID, suspended app, outdated version, unregistered runtime build, maintenance.
AuthenticationFailed The key was refused: wrong, expired, banned, or no free device seat. The server does not say which.
NotInitialized, NotLoggedIn A call came too early.
NotFound No variable with that id or name for this license.
NativeTooOld The runtime predates the call; see Supports().
AttestationFailed, KeyExchangeFailed, DecryptFailed The runtime could not prove it holds the session key, or a value did not decrypt. Treat as tampering.
InvalidValue A variable did not parse as the requested type.
ServerUnreachable No connection, and this device cannot start offline (offline start is off, it never signed in online here, or that was with another key).
OfflineExpired No connection, and this device's offline time (or the license) has run out.
ClockInvalid No connection, and the system clock reads earlier than the last run.
OfflineUnavailable The call needs a connection and the app is running offline (files).

The full list, with numbers, is in src/ShieldySdk/ShieldyError.cs.

Offline start

When the app has offline start on (dashboard: Security → Availability), a device that signed in online can start without internet for the number of days the app allows. Init and Login succeed offline as they do online, so nothing has to change. To show it:

ShieldyConnectionState state = Shieldy.GetConnectionState();
if (state.IsOffline)
    ShowBanner($"Offline - works until {state.OfflineUntil:MMM d}");

Offline, the license, variables and Protected Strings work; files need a connection. The device has to sign in with the key it last used online. For a portable install, set new ShieldyConfig(...) { LeaseDirectory = "leases" }. To test without pulling the cable, set SHIELDY_DEV_FORCE_OFFLINE=1 with a development build of the runtime.

Trying another key

When a key is refused, Login() can be called again with another one, and Init() works again after Shutdown(). Runtimes built before October 2026 allowed one sign-in per process (the server ends the session at a refused key and they could not open another); with one of those, restart the process to let the user try another key.

Files and memory

GetFileById() returns a SecureBuffer: the decrypted bytes in one array that is pinned for its whole life (so the garbage collector never leaves a moved copy behind) and zeroed by Dispose() or, if you forget, by the finalizer. Read it through the buffer (OpenRead(), the indexer, CopyTo(), AsSpan() on .NET 8+); ToArray() makes a copy the SDK cannot wipe. This is best effort, as it has to be in .NET: copies your own code or a library makes (a string from GetString(), for instance) are out of the SDK's reach.

Protected Strings in the build

Write secrets as SHIELDY_OBF("...") (a regular string literal) and keep coding against the plain text. The package adds the marker to every C# project that references it:

using ShieldySdk;
using static ShieldySdk.ShieldyObfMarker;

static string ApiEndpoint() { return SHIELDY_OBF("https://api.example.com/v2"); }

Before a release, run the dashboard's CLI over each file. It rewrites every marker to Shieldy.GetProtectedString("<ciphertext>"):

python tools/shieldy_strings_cli.py --api-url https://<dashboard>/api --app-guid <App GUID> `
    --api-key <key with apps:write> --file src/Endpoints.cs --language csharp --in-place

A marker left in the code does not compile (error CS0619), the same guarantee as the C++ SDK's static_assert, so a literal you meant to protect cannot ship in the clear. For debug builds define SHIELDY_OBF_PLAINTEXT and the marker returns the literal itself:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <DefineConstants>$(DefineConstants);SHIELDY_OBF_PLAINTEXT</DefineConstants>
</PropertyGroup>

Never define it for a release build. Verbatim (@"..."), interpolated ($"...") and concatenated literals are not rewritten by the CLI; they stay markers and fail the build. Read protected strings where they are used, after Login(), never in a static field initializer. To leave the marker out of a project, set <ShieldyObfMarker>false</ShieldyObfMarker>. See examples/ProtectedStringsMarker.

Examples

  • examples/Basic: the whole flow, configured from environment variables (SHIELDY_APP_GUID, SHIELDY_LICENSE_KEY, optional SHIELDY_VARIABLE_ID, SHIELDY_FILE_ID, SHIELDY_PROTECTED_STRING). Builds for .NET Framework 4.8 and .NET 10.
  • examples/ProtectedStringsMarker: the SHIELDY_OBF workflow. Its Release build fails until the CLI has run, on purpose, which is why it is not in the solution.

Known interactions

  • Bouncy Castle. The SDK depends on BouncyCastle.Cryptography 2.x. An application that also references the old Portable.BouncyCastle package gets both, and the two declare the same Org.BouncyCastle types; move it to BouncyCastle.Cryptography, its successor.
  • InternalsVisibleTo between two of your projects that both reference the SDK makes the compiler see ShieldyObfMarker twice (warning CS0436, harmless). Set <ShieldyObfMarker>false</ShieldyObfMarker> in the project that does not use the marker.

Working on this repository

dotnet build ShieldySdk.sln
dotnet test tests/ShieldySdk.Tests          # unit tests on net462, net8.0, net10.0 against a mock runtime
dotnet test tests/ShieldySdk.E2E            # a real runtime against a real server, see its README
powershell -File scripts/check-consumers.ps1    # pack, then build customer-style projects from the .nupkg
dotnet pack src/ShieldySdk -c Release -o artifacts/feed

How the SDK works inside, why it is built the way it is, and what to touch for a change: docs/architecture.md. Orientation for an AI assistant (and a quick map for anyone): CLAUDE.md.

third_party/shieldy_api.h is a copy of ShieldyCore's cpp-module/shieldy_api.h, the ABI the SDK mirrors in src/ShieldySdk/Internal/Abi.cs. AbiTests fails when the two disagree, and when the copy differs from a ShieldyCore checkout next to this repository.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 2 11/1/2026
1.0.0 1 10/31/2026