This guide shows how to implement file-based credential workflows in Safeguard custom platforms. Use it when the credential you manage is a file rather than a password, SSH key, or API key.
- What file management means in Safeguard
- Core operations
- Reserved parameter and feature flag behavior
- Common use cases
- Recommended custom parameters
- Implementing
CheckFile - Implementing
ChangeFile - SSH pattern: stage, deploy, verify, clean up
- HTTP pattern: upload through an API
- How to decode base64 safely
- Verification strategies
- Error handling patterns
- Security considerations
- Related references
In Safeguard, file management means managing credentials whose authoritative value is a file payload. Instead of rotating a password string, your custom platform checks or deploys the contents of a file that Safeguard stores securely.
Typical examples include:
- TLS/SSL certificates and certificate bundles
- application config files with embedded credentials
- Java keystores
- license files that expire and need rotation
- SSH private key files managed as files rather than through
CheckSshKey/ChangeSshKey
Safeguard stores the file in the secure file vault, then passes the file content to your script at runtime. Your script is responsible for putting that content in the right place, validating it, and returning success or failure.
File workflows use two operations from the Operations Reference:
| Operation | Purpose | Typical success condition |
|---|---|---|
CheckFile |
Verify that the current file on the target matches the file stored in Safeguard. | The deployed file is current, valid, and in the expected location. |
ChangeFile |
Deploy a new file from Safeguard to the target system. | The new file is written successfully and post-change verification succeeds. |
A common pattern is:
CheckFilecompares the target's current state to the expected file.ChangeFilestages and deploys the new file.ChangeFileverifies the deployment before returningtrue.- A later
CheckFileconfirms the target stays in sync.
The key reserved parameter is FileBase64String.
| Parameter | Type | Source | Meaning |
|---|---|---|---|
FileBase64String |
Secret |
Safeguard account secure file vault | Base64-encoded content of the file being checked or changed |
Important details:
FileBase64Stringis auto-populated. Administrators do not type it into Custom Script Parameters.- The value is a base64-encoded string, so your script must either decode it or pass it to a target/API that decodes it.
- Treat it as sensitive data. Use
IsSecret,InputContainsSecret,CommandContainsSecret, and similar masking options when needed.
FileFeatureFl is a special case. As documented in Operations, it is always true for custom platforms. You do not enable file support by declaring a parameter or setting a flag manually.
However, the operations still matter:
FileFeatureFlbeing on does not create runnable behavior by itself.- You still need to define
CheckFileand/orChangeFilein your script if you want Safeguard to perform those tasks.
| Use case | What CheckFile usually verifies |
What ChangeFile usually does |
|---|---|---|
| TLS/SSL certificate deployment | Thumbprint, serial number, issuer, expiration, file permissions | Replace certificate/key bundle, then validate certificate metadata |
| Config file with embedded credentials | Exact content or normalized checksum, syntax validity, owner/mode | Write updated config, validate syntax, optionally restart/reload service |
Java keystore (.jks, .p12) |
Keystore exists, checksum matches, alias/certificate is present | Upload keystore, verify with keytool, preserve permissions |
| License file rotation | File exists, checksum matches, target reports active/non-expired license | Replace license file, call reload/import endpoint, confirm active status |
| SSH private key file | Exact bytes, owner, mode, optional fingerprint | Write key file, set restrictive permissions, verify fingerprint/access |
FileBase64String gives you the file content, but it does not tell your script where or how to deploy it. Most file-management platforms also define custom parameters such as:
| Parameter | Why it is useful |
|---|---|
TargetPath |
Absolute path of the deployed file on the target system |
TargetOwner / TargetGroup |
Owner and group that should own the file after deployment |
TargetMode |
Required file mode such as 600 or 640 |
ReloadCommand |
Optional command or API action to reload the application after update |
ApiFileId / CertificateAlias |
Target-side identifier used by an HTTP API or keystore tool |
Keep these as normal custom parameters unless Safeguard already provides a matching reserved parameter.
CheckFile should answer one question: does the target currently have the right file?
Good CheckFile implementations are read-only and explicit. They usually:
- locate the deployed file or target-side object
- decode or otherwise interpret
FileBase64String - compare the expected content to the current target content
- validate extra properties such as permissions, ownership, alias, or expiry
- return
trueonly when everything matches
A minimal parameter block often looks like this:
"CheckFile": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "Port": { "Type": "Integer", "Required": false, "DefaultValue": 22 } },
{ "Timeout": { "Type": "Integer", "Required": false, "DefaultValue": 20 } },
{ "FuncUserName": { "Type": "String", "Required": true } },
{ "FuncPassword": { "Type": "Secret", "Required": false } },
{ "AccountUserName": { "Type": "String", "Required": true } },
{ "FileBase64String": { "Type": "Secret", "Required": true } },
{ "TargetPath": { "Type": "String", "Required": true } }
],
"Do": [
{ "Comment": { "Text": "Read the deployed file and compare it with FileBase64String" } }
]
}Use exact byte-for-byte comparison when the raw file must match exactly. Use semantic validation when the target normalizes the file during import, such as certificate stores or APIs that reformat uploaded content.
ChangeFile should safely deploy a new file and prove that the deployment worked.
A reliable pattern is:
- connect or authenticate with the service account
- stage the new file in a protected temporary location
- copy or import it into the real target location
- set ownership and permissions
- verify the deployed result
- clean up the staging file
- return
true
If rollback is practical, take a backup before replacing the current file. This is especially useful for certificates, keystores, and application configs.
For SSH-based platforms, the safest general pattern is:
- connect with the service account
- write
FileBase64Stringto stdin of a remote decode command - decode into a protected staging file
- install or move that file to the real destination
- verify content and permissions
- remove the staging file in
Finally
This keeps the base64 payload out of the command line and preserves binary content exactly.
{
"CheckFile": {
"Parameters": [
{ "Address": { "Type": "String", "Required": true } },
{ "Port": { "Type": "Integer", "Required": false, "DefaultValue": 22 } },
{ "Timeout": { "Type": "Integer", "Required": false, "DefaultValue": 20 } },
{ "FuncUserName": { "Type": "String", "Required": true } },
{ "FuncPassword": { "Type": "Secret", "Required": false } },
{ "AccountUserName": { "Type": "String", "Required": true } },
{ "FileBase64String": { "Type": "Secret", "Required": true } },
{ "TargetPath": { "Type": "String", "Required": true } }
],
"Do": [
{
"Connect": {
"ConnectionObjectName": "Global:ConnectSsh",
"Type": "Ssh",
"NetworkAddress": "%Address%",
"Port": "%Port%",
"Login": "%FuncUserName%",
"Password": "%FuncPassword::$%",
"RequestTerminal": false,
"Timeout": "%Timeout%"
}
},
{
"Try": {
"Do": [
{
"ExecuteCommand": {
"ConnectionObjectName": "ConnectSsh",
"Command": "umask 077; mkdir -p /var/lib/sg-stage; base64 -d > /var/lib/sg-stage/expected.bin",
"Stdin": [ "%FileBase64String%" ],
"BufferName": "DecodeStdout",
"StderrBufferName": "DecodeStderr",
"ExitStatusBufferName": "DecodeRc",
"InputContainsSecret": true
}
},
{
"Condition": {
"If": "DecodeRc != 0",
"Then": {
"Do": [
{ "Throw": { "Value": "Unable to decode expected file: %{ DecodeStderr }%" } }
]
}
}
},
{
"ExecuteCommand": {
"ConnectionObjectName": "ConnectSsh",
"Command": "cmp -s /var/lib/sg-stage/expected.bin %TargetPath%",
"BufferName": "CompareStdout",
"StderrBufferName": "CompareStderr",
"ExitStatusBufferName": "CompareRc"
}
},
{
"Condition": {
"If": "CompareRc == 0",
"Then": { "Do": [ { "Return": { "Value": true } } ] },
"Else": { "Do": [ { "Throw": { "Value": "File content does not match target file" } } ] }
}
}
],
"Finally": [
{
"ExecuteCommand": {
"ConnectionObjectName": "ConnectSsh",
"Command": "rm -f /var/lib/sg-stage/expected.bin",
"BufferName": "CleanupStdout",
"StderrBufferName": "CleanupStderr",
"ExitStatusBufferName": "CleanupRc"
}
},
{ "Disconnect": { "ConnectionObjectName": "ConnectSsh" } }
]
}
}
]
}
}{
"ExecuteCommand": {
"ConnectionObjectName": "ConnectSsh",
"Command": "umask 077; mkdir -p /var/lib/sg-stage; base64 -d > /var/lib/sg-stage/newfile.bin",
"Stdin": [ "%FileBase64String%" ],
"BufferName": "DecodeStdout",
"StderrBufferName": "DecodeStderr",
"ExitStatusBufferName": "DecodeRc",
"InputContainsSecret": true
}
}
{
"ExecuteCommand": {
"ConnectionObjectName": "ConnectSsh",
"Command": "install -m 600 /var/lib/sg-stage/newfile.bin %TargetPath%",
"BufferName": "InstallStdout",
"StderrBufferName": "InstallStderr",
"ExitStatusBufferName": "InstallRc"
}
}In real platforms, add ownership changes, backup logic, privilege escalation such as sudo, and post-deployment verification before returning success. The general SSH building blocks are covered in the SSH Platforms Guide.
For HTTP platforms, the usual pattern is to send the file content to an upload endpoint with POST or PUT, then verify the target-side object through a follow-up API call.
In many cases, the simplest API contract is to keep the file base64-encoded and send it in JSON. That avoids lossy text conversions and works well for binary files.
{
"SetItem": {
"Name": "UploadBody",
"Value": {
"path": "%TargetPath%",
"owner": "%AccountUserName%",
"fileBase64": "%FileBase64String%"
},
"IsSecret": true
}
}
{
"NewHttpRequest": { "ObjectName": "UploadRequest" }
}
{
"Headers": {
"RequestObjectName": "UploadRequest",
"AddHeaders": {
"Authorization": "Bearer %AccessToken%",
"Accept": "application/json"
}
}
}
{
"Request": {
"RequestObjectName": "UploadRequest",
"ResponseObjectName": "UploadResponse",
"Verb": "PUT",
"Url": "/api/v1/files",
"Content": {
"ContentObjectName": "UploadBody",
"ContentType": "application/json"
}
}
}
{
"Condition": {
"If": "UploadResponse.StatusCode.ToString().Equals(\"OK\")",
"Then": { "Do": [ { "Return": { "Value": true } } ] },
"Else": { "Do": [ { "Throw": { "Value": "Upload failed: HTTP %{ UploadResponse.StatusCode }%" } } ] }
}
}Do not treat an upload 200 OK by itself as proof that the file is active. In a production ChangeFile, follow the upload with a metadata read, checksum query, certificate lookup, or other verification step, then return true only after that verification succeeds.
If the API imports the file into a certificate store, keystore, or licensing subsystem, CheckFile should query the API for the imported object's metadata rather than expecting the raw uploaded bytes to remain unchanged.
For general HTTP transport patterns, see the HTTP Platforms Guide.
There is no dedicated public Base64Decode command in the documented command set. In practice, file-management scripts usually use one of these approaches.
Pass %FileBase64String% as stdin to a remote command and let the target decode it with native tools. This preserves binary data and keeps the payload off the command line.
Examples of target-side decode commands include:
- Linux or Unix:
base64 -d - Windows PowerShell:
[System.Convert]::FromBase64String(...) - Windows certutil:
certutil -decode
If the target API can accept a base64 field, do not decode it inside the script at all. Send the base64 value as JSON and let the server-side endpoint decode it.
For text files such as config fragments, you can decode the payload inside a SetItem expression:
{
"SetItem": {
"Name": "DecodedText",
"Value": "%{ System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String(FileBase64String)) }%",
"IsSecret": true
}
}Use this only when the target value is truly UTF-8 text. Do not use it for binary files such as PKCS#12 bundles, Java keystores, or opaque license blobs.
Choose a verification pattern that matches how the target system stores the file.
Best when the deployed file should match byte-for-byte.
Common approaches:
- decode the expected payload to a staging file and compare with
cmp - compute a checksum for both files and compare the digest
- use API-returned digests when the target exposes them
Best for PEM, CRT, PFX, or keystore-backed certificate workflows.
Common checks:
- serial number
- thumbprint or fingerprint
- subject / issuer
- not-before and not-after dates
- expected alias in a keystore
Typical target-side tools include openssl x509, openssl pkcs12, or keytool -list.
Best when location and filesystem metadata matter as much as content.
Common checks:
- file exists and is non-empty
- expected owner and group are set
- restrictive mode such as
600or640is present - parent directory is correct and not world-readable
Best when the target imports the file into an internal store.
Examples:
- query an API for the active certificate thumbprint
- run an application config test command
- check that the target now reports the new license as active
- call a reload endpoint and confirm the service accepted the new file
File deployment failures are often operational rather than logical. Surface them clearly.
| Failure | Typical signal | Recommended response |
|---|---|---|
| Permission denied | SSH stderr contains Permission denied; API returns 403 or similar |
Throw a clear error that names the target path or API resource and the identity used |
| Disk full / no space left | Exit code non-zero; stderr contains No space left on device |
Fail immediately; do not report success just because decode or upload started |
| File in use / locked | Replace or rename fails; API reports conflict | Retry only if the platform supports a safe retry window; otherwise throw |
| Verification failure | File write succeeds but checksum, cert, or metadata check fails | Throw a verification-specific error and, if possible, restore from backup |
| Cleanup failure | Temp file removal fails | Prefer Finally cleanup; if cleanup fails after a successful deployment, surface a warning/error that explains what remains |
A simple Try / Catch wrapper for a risky deployment step looks like this:
{
"Try": {
"Do": [
{
"ExecuteCommand": {
"ConnectionObjectName": "ConnectSsh",
"Command": "install -m 600 /var/lib/sg-stage/newfile.bin %TargetPath%",
"BufferName": "InstallStdout",
"StderrBufferName": "InstallStderr",
"ExitStatusBufferName": "InstallRc"
}
},
{
"Condition": {
"If": "InstallRc != 0",
"Then": {
"Do": [
{ "Throw": { "Value": "File deployment failed: %{ InstallStderr }%" } }
]
}
}
}
],
"Catch": [
{ "Throw": { "Value": "ChangeFile failed for %TargetPath%: %Exception%" } }
]
}
}For exact syntax, see error handling commands.
File-based credentials are often long-lived, high-value secrets. Handle them like passwords or private keys.
- Use a protected staging location with restrictive permissions.
- Clean up staging files in
Finally, not only on the success path. - Avoid leaving decoded secrets in world-readable locations.
- Prefer stdin over command-line arguments for
%FileBase64String%so the payload does not appear in process listings. - Mark intermediate variables and request bodies as secret when they contain file content.
- Verify ownership and permissions after deployment, not just content.
- Do not convert binary payloads to text unless the file format is actually textual.
- If you take backups, protect them with the same permissions as the active file and clean them up according to your operational policy.