Files
PSInfisicalAPI/docs/DesignSpec.md
T
GraceSolutions dce97e98de
Publish to PowerShell Gallery / build (pull_request) Failing after 6s
Publish to PowerShell Gallery / release (pull_request) Has been skipped
Publish to PowerShell Gallery / publish (pull_request) Has been skipped
Default -ViewSecretValue to true; reject <hidden-by-infisical> placeholder
Get-InfisicalSecrets and Get-InfisicalSecret now return real secret values by default. Pass -ViewSecretValue:False to opt in to the server's hidden response. InfisicalSecretMapper detects the <hidden-by-infisical> placeholder and the secretValueHidden flag; in either case SecretValue is set to null instead of pushing the literal placeholder into a SecureString, so downstream auth/export/dictionary consumers can never silently use the placeholder as if it were a real secret.
2026-06-02 21:34:16 -04:00

44 KiB
Raw Blame History

PSInfisicalAPI Full Specification

1. Project Summary

PSInfisicalAPI is a C# binary PowerShell module for interacting with the Infisical REST API. It is inspired by PSInfisical, but it is not a PowerShell-only implementation. The module must be built as a compiled C# module targeting .NET Standard 2.0 so it works in both Windows PowerShell 5.1 and PowerShell 7+.

The goal is to establish a strong, reusable, secure framework first, then initially implement secret retrieval and export workflows.

Initial public cmdlets:

Connect-Infisical
Disconnect-Infisical
Get-InfisicalSecrets
Get-InfisicalSecret
ConvertTo-InfisicalSecretDictionary
Export-InfisicalSecrets

Infisicals public API is REST-based and provides programmatic access for managing secrets and related resources. Current Infisical documentation shows the list-secrets endpoint under /api/v4/secrets, the single-secret retrieval endpoint under /api/v4/secrets/{secretName}, and Universal Auth login under /api/v1/auth/universal-auth/login. The implementation must centralize API endpoint definitions because Infisical uses different API versions across resource families. (Infisical Blog)


2. Non-Negotiable Requirements

2.1 Runtime

The module must target:

.NET Standard 2.0
PowerShellStandard.Library
Windows PowerShell 5.1
PowerShell 7+

2.2 No Async/Await

The codebase must contain no usage of:

async
await

All HTTP calls, file writes, serialization, logging, and export operations must be synchronous.

2.3 Centralized Reusable Logic

No double implementation.

The following must be centralized:

Logging
Error handling
Endpoint definitions
URI construction
Query string construction
HTTP request execution
Authentication
Response parsing
Secret conversion
SecureString handling
Export formatting
Path handling
Version handling
Build/release handling

No endpoint URL, API path, query construction logic, or authentication flow should be scattered across cmdlets.

2.4 Secret Safety

The module must prioritize limiting secret exposure in memory.

Rules:

Never log secret values.
Never log client secrets.
Never log access tokens.
Never log Authorization headers.
Never log raw request bodies that contain secrets.
Never log raw API response bodies that contain secrets.
Never expose plaintext secret values as public object properties.
Never expose plaintext through ToString().
Never expose raw API response JSON from public cmdlets.
Convert secret values to SecureString as quickly as practical.
Call MakeReadOnly() on SecureString values after population.
Clear temporary API response objects as aggressively as practical.
Clear temporary plaintext variables as aggressively as practical.

The module should document the unavoidable .NET limitation: once a secret exists as a managed string, that memory cannot be reliably zeroed. Therefore, the design goal is to avoid unnecessary copies, avoid logging, keep plaintext scope short, and move values into read-only SecureString objects as quickly as practical.

2.5 No Export Warnings

Do not emit warning messages for export operations.

The user intentionally requested export support. The module should silently perform the export, while still ensuring exported values are never written to verbose/debug/error logs.

This applies to:

JSON
YAML
ENV
XML
EnvironmentVariables

3. Repository Structure

The repository must follow this structure:

PSInfisicalAPI/
├── Artifacts/
├── Module/
│   └── PSInfisicalAPI/
│       ├── PSInfisicalAPI.psd1
│       ├── PSInfisicalAPI.psm1
│       ├── PSInfisicalAPI.Format.ps1xml
│       ├── PSInfisicalAPI.Types.ps1xml
│       └── bin/
│           ├── PSInfisicalAPI.dll
│           ├── Newtonsoft.Json.dll
│           └── YamlDotNet.dll
├── Releases/
│   └── yyyy.MM.dd.HHmm/
├── docs/
│   ├── about_PSInfisicalAPI.help.txt
│   ├── Connect-Infisical.md
│   ├── Disconnect-Infisical.md
│   ├── Get-InfisicalSecrets.md
│   ├── Get-InfisicalSecret.md
│   ├── ConvertTo-InfisicalSecretDictionary.md
│   └── Export-InfisicalSecrets.md
├── src/
│   ├── PSInfisicalAPI/
│   │   ├── Authentication/
│   │   ├── Cmdlets/
│   │   ├── Common/
│   │   ├── Connections/
│   │   ├── Endpoints/
│   │   ├── Errors/
│   │   ├── Exports/
│   │   ├── Http/
│   │   ├── Logging/
│   │   ├── Models/
│   │   ├── Secrets/
│   │   ├── Security/
│   │   └── Serialization/
│   └── PSInfisicalAPI.Tests/
├── build.ps1
├── CHANGELOG.md
└── README.md

Source starts under /src.

Namespaces should follow responsibility and folder depth, for example:

PSInfisicalAPI.Authentication
PSInfisicalAPI.Cmdlets
PSInfisicalAPI.Endpoints
PSInfisicalAPI.Security
PSInfisicalAPI.Serialization

4. Module Files

4.1 PSD1

The manifest must be generated by the build script.

Example shape:

@{
    RootModule = 'PSInfisicalAPI.psm1'
    ModuleVersion = 'yyyy.MM.dd.HHmm'
    GUID = '<stable-guid>'
    Author = 'Grace Solutions'
    CompanyName = ''
    Copyright = ''
    PowerShellVersion = '5.1'
    CompatiblePSEditions = @('Desktop', 'Core')
    FunctionsToExport = @()
    CmdletsToExport = @(
        'Connect-Infisical',
        'Disconnect-Infisical',
        'Get-InfisicalSecrets',
        'Get-InfisicalSecret',
        'ConvertTo-InfisicalSecretDictionary',
        'Export-InfisicalSecrets'
    )
    AliasesToExport = @()
    PrivateData = @{
        PSData = @{
            Tags = @('Infisical', 'Secrets', 'API', 'SecureString')
            ProjectUri = ''
            ReleaseNotes = ''
            CommitHash = '<git-commit-hash>'
        }
    }
}

4.2 PSM1

The .psm1 must be minimal and only load the binary module plus format/type data.

$BinaryPath = [System.IO.FileInfo][System.IO.Path]::Combine($PSScriptRoot, 'bin', 'PSInfisicalAPI.dll')

Import-Module -Name $BinaryPath.FullName

$TypesPath = [System.IO.FileInfo][System.IO.Path]::Combine($PSScriptRoot, 'PSInfisicalAPI.Types.ps1xml')
$FormatPath = [System.IO.FileInfo][System.IO.Path]::Combine($PSScriptRoot, 'PSInfisicalAPI.Format.ps1xml')

if ([System.IO.File]::Exists($TypesPath.FullName)) {
    Update-TypeData -PrependPath $TypesPath.FullName -ErrorAction SilentlyContinue
}

if ([System.IO.File]::Exists($FormatPath.FullName)) {
    Update-FormatData -PrependPath $FormatPath.FullName -ErrorAction SilentlyContinue
}

5. Versioning

Version format:

yyyy.MM.dd.HHmm

Example:

2026.06.02.2140

The version must be generated once per build and applied consistently to:

PSD1 ModuleVersion
AssemblyVersion
AssemblyFileVersion
AssemblyInformationalVersion
Release folder
CHANGELOG.md
Generated docs if applicable

The git commit hash must be embedded separately:

PSD1 PrivateData.PSData.CommitHash
AssemblyMetadata("CommitHash", "<commit-hash>")
AssemblyInformationalVersion = "yyyy.MM.dd.HHmm"

6. Build Script Specification

The build script must be:

Idempotent
Repeatable
Safe to run multiple times
Responsible for versioning
Responsible for manifest generation
Responsible for release folder creation
Responsible for module folder creation
Responsible for copying binaries
Responsible for optional commit-on-success

6.1 Build Script Parameters

param(
    [ValidateSet('Debug', 'Release')]
    [string]$Configuration = 'Release',

    [switch]$Clean,

    [switch]$Restore,

    [switch]$RunTests,

    [switch]$RunIntegrationTests,

    [switch]$CreateRelease,

    [switch]$CommitOnSuccess,

    [switch]$Force
)

6.2 Required Behavior

Create missing folders.
Clean generated output when -Clean is used.
Never delete source files.
Generate version once.
Read current git commit hash.
Restore packages when requested.
Build the C# project.
Run unit tests when requested.
Run integration tests only when explicitly requested.
Generate PSD1.
Generate PSM1 if missing or when -Force is used.
Copy compiled DLLs to Module/PSInfisicalAPI/bin.
Copy dependency DLLs to Module/PSInfisicalAPI/bin.
Copy type and format files.
Update CHANGELOG.md.
Create Releases/yyyy.MM.dd.HHmm when requested.
Overwrite same-version release only when -Force is used.
Validate module import where possible.
Commit only after successful build when -CommitOnSuccess is specified.
Never commit failed builds.

6.3 Example Usage

.\build.ps1 -Clean -Restore -RunTests -CreateRelease -CommitOnSuccess
.\build.ps1 -Clean -Restore -RunTests -RunIntegrationTests -CreateRelease

6.4 Periodic Build and Commit Rule

After each logical milestone:

Build.
Test.
If successful, commit.
If failed, do not commit.

Suggested commit messages:

Add module skeleton and build script
Add centralized logging and error handling
Add endpoint registry and URI builder
Add connection manager and auth providers
Add secret retrieval cmdlets
Add secret export providers
Add integration test support

7. Test Instance Configuration

Integration tests must read connection values from any scope environment variables with precendence of Process, User, Machine. First found wins.

Target:

[System.EnvironmentVariableTarget]::Machine

Required variables:

CLOUDINIT_INFISICAL_APIURL
CLOUDINIT_INFISICAL_ORGANIZATIONID
CLOUDINIT_INFISICAL_PROJECTID
CLOUDINIT_INFISICAL_ENVIRONMENT
CLOUDINIT_INFISICAL_CLIENTID
CLOUDINIT_INFISICAL_CLIENTSECRET

7.1 Integration Test Rules

Integration tests must not run by default.
Integration tests run only with -RunIntegrationTests.
Missing test environment variables should skip integration tests or fail with a sanitized setup error.
Do not log CLOUDINIT_INFISICAL_CLIENTSECRET.
Do not log access tokens returned by Infisical.
Convert CLOUDINIT_INFISICAL_CLIENTSECRET to SecureString immediately.
Call MakeReadOnly() after SecureString population.
Clear temporary string references as aggressively as practical.

Example internal loading pattern:

string apiUrl = Environment.GetEnvironmentVariable("CLOUDINIT_INFISICAL_APIURL", EnvironmentVariableTarget.Machine);
string organizationId = Environment.GetEnvironmentVariable("CLOUDINIT_INFISICAL_ORGANIZATIONID", EnvironmentVariableTarget.Machine);
string projectId = Environment.GetEnvironmentVariable("CLOUDINIT_INFISICAL_PROJECTID", EnvironmentVariableTarget.Machine);
string environment = Environment.GetEnvironmentVariable("CLOUDINIT_INFISICAL_ENVIRONMENT", EnvironmentVariableTarget.Machine);
string clientId = Environment.GetEnvironmentVariable("CLOUDINIT_INFISICAL_CLIENTID", EnvironmentVariableTarget.Machine);
string clientSecretPlainText = Environment.GetEnvironmentVariable("CLOUDINIT_INFISICAL_CLIENTSECRET", EnvironmentVariableTarget.Machine);

SecureString clientSecret = SecureStringUtility.ToReadOnlySecureString(clientSecretPlainText);

clientSecretPlainText = null;

8. Logging Specification

Logging must be centralized.

Format:

[UTC Timestamp] - [Level] - [Component] - Message

Example:

[2026-06-02T21:44:22.1830000Z] - [Information] - [SecretsClient] - Attempting to retrieve Infisical secrets. Please Wait...
[2026-06-02T21:44:22.9290000Z] - [Information] - [SecretsClient] - Infisical secrets retrieval was successful.
[2026-06-02T21:44:22.9330000Z] - [Error] - [SecretsClient] - Infisical secrets retrieval failed.

8.1 Required Log Levels

Information
Verbose
Debug
Warning
Error

Warning exists as a log level but must not be used for intentional export operations.

8.2 Operation Logging

For meaningful operations, log:

Attempting to ...
... was successful.
... failed.

Use Please Wait... where appropriate.

Examples:

Attempting to authenticate to Infisical. Please Wait...
Infisical authentication was successful.
Infisical authentication failed.

Attempting to retrieve Infisical secrets. Please Wait...
Infisical secrets retrieval was successful.
Infisical secrets retrieval failed.

Attempting to export Infisical secrets to JSON. Please Wait...
Infisical secrets export to JSON was successful.
Infisical secrets export to JSON failed.

8.3 PowerShell Channels

Logging should map to native PowerShell output channels:

Verbose logs -> WriteVerbose
Debug logs -> WriteDebug
Warnings -> WriteWarning
Errors -> WriteError

Operational logs should respect -Verbose.


9. Error Handling Specification

All error handling must be centralized.

Required types:

InfisicalException
InfisicalApiException
InfisicalAuthenticationException
InfisicalHttpException
InfisicalSerializationException
InfisicalExportException
InfisicalConfigurationException
InfisicalErrorDetails
InfisicalErrorHandler

9.1 Error Details

Errors should preserve:

Component
Operation
Message
Exception type
Inner exception message
HTTP status code
HTTP reason phrase
API error code when available
Sanitized API error body when safe
JSON line number when available
JSON position when available
Request endpoint key
Request method

9.2 Error Logging

When a failure occurs, log multiple sanitized details where possible:

[UTC] - [Error] - [ErrorHandler] - Operation failed: RetrieveSecret
[UTC] - [Error] - [ErrorHandler] - Error Component: SecretsClient
[UTC] - [Error] - [ErrorHandler] - Error Message: The Infisical API returned Forbidden.
[UTC] - [Error] - [ErrorHandler] - HTTP Status Code: 403
[UTC] - [Error] - [ErrorHandler] - API Error Code: <if available>
[UTC] - [Error] - [ErrorHandler] - Line: <if available>
[UTC] - [Error] - [ErrorHandler] - Position: <if available>

9.3 PowerShell Error Records

Cmdlets must emit proper ErrorRecord objects.

Examples:

CategoryInfo:
  AuthenticationError
  ConnectionError
  InvalidData
  InvalidOperation
  PermissionDenied
  ResourceUnavailable
  WriteError

The real underlying error must bubble up, but sanitized to avoid exposing secrets.


10. Endpoint Registry

All endpoint definitions must be centralized.

No cmdlet may hard-code endpoint paths.

10.1 Endpoint Definition Model

public sealed class InfisicalEndpointDefinition
{
    public string Name { get; set; }
    public string Resource { get; set; }
    public string Version { get; set; }
    public string Method { get; set; }
    public string Template { get; set; }
    public bool RequiresAuthorization { get; set; }
    public bool ContainsSecretMaterialInRequest { get; set; }
    public bool ContainsSecretMaterialInResponse { get; set; }
}

10.2 Initial Endpoint Definitions

UniversalAuthLogin:
  Method: POST
  Version: v1
  Template: /api/v1/auth/universal-auth/login
  RequiresAuthorization: false
  ContainsSecretMaterialInRequest: true
  ContainsSecretMaterialInResponse: true

ListSecrets:
  Method: GET
  Version: v4
  Template: /api/v4/secrets
  RequiresAuthorization: true
  ContainsSecretMaterialInRequest: false
  ContainsSecretMaterialInResponse: true

RetrieveSecret:
  Method: GET
  Version: v4
  Template: /api/v4/secrets/{secretName}
  RequiresAuthorization: true
  ContainsSecretMaterialInRequest: false
  ContainsSecretMaterialInResponse: true

Universal Auth uses a client ID and client secret to obtain an access token, and Infisical documents the login endpoint as /api/v1/auth/universal-auth/login. (Infisical Blog)

10.3 API Version Flexibility

Connect-Infisical should accept:

-ApiVersion

Default:

v4

However, API version must not be assumed globally for every resource. The endpoint registry must allow each endpoint family to specify its own version.


11. URI and Path Handling

11.1 URI Rules

All URLs must use:

System.Uri
System.UriBuilder

URI construction must be centralized in:

InfisicalUriBuilder

Responsibilities:

Combine base URI and endpoint path.
Escape path segments.
Escape query parameters.
Support repeated query parameters.
Avoid manual string concatenation.
Preserve scheme/host/port.
Support Linux, macOS, and Windows.

11.2 Path Rules

Internal filesystem paths must use:

System.IO.FileInfo
System.IO.DirectoryInfo
System.IO.Path.Combine(...)

PowerShell build/helper scripts must use:

[System.IO.FileInfo][System.IO.Path]::Combine(...)
[System.IO.DirectoryInfo][System.IO.Path]::Combine(...)

Public command examples can use simple strings because PowerShell can bind strings to FileInfo, DirectoryInfo, and Uri.

Public example:

Export-InfisicalSecrets -Format Env -Path '.\secrets.env'

Internal implementation must still use proper typed path handling.


12. Authentication Design

12.1 Supported Initial Auth Types

Initial implementation:

Universal Auth
Token Auth

Infisical documents identity authentication modes such as Universal Auth and Token Auth for API access, and API interaction requires an access token. (Infisical Blog)

12.2 Future Auth Types

Design must allow future support for:

AWS Auth
Azure Auth
GCP Auth
Kubernetes Auth
OIDC Auth
JWT Auth
LDAP Auth
TLS Certificate Auth
Alibaba Cloud Auth
OCI Auth

These should not be exposed as public parameter sets until actually implemented.

12.3 Auth Provider Interface

public interface IInfisicalAuthProvider
{
    string Name { get; }

    InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger);
}

12.4 Authentication Result

public sealed class InfisicalAuthenticationResult
{
    public SecureString AccessToken { get; set; }
    public DateTimeOffset? ExpiresAtUtc { get; set; }
    public string TokenType { get; set; }
}

AccessToken must be read-only.


13. Connection Management

The module must maintain a process-level current connection.

13.1 Session Manager

public static class InfisicalSessionManager
{
    public static InfisicalConnection Current { get; }

    public static void SetCurrent(InfisicalConnection connection);

    public static InfisicalConnection RequireCurrent();

    public static void Disconnect();
}

13.2 Connection Model

public sealed class InfisicalConnection
{
    public Uri BaseUri { get; set; }
    public string ApiVersion { get; set; }
    public InfisicalAuthType AuthType { get; set; }
    public string OrganizationId { get; set; }
    public string ProjectId { get; set; }
    public string Environment { get; set; }
    public string DefaultSecretPath { get; set; }
    public DateTimeOffset ConnectedAtUtc { get; set; }
    public DateTimeOffset? ExpiresAtUtc { get; set; }
    public bool IsConnected { get; set; }

    internal SecureString AccessToken { get; set; }
}

The public object must not display or serialize AccessToken.


14. Public Cmdlet Specifications

14.1 Connect-Infisical

Purpose

Authenticate to Infisical and store the current connection.

Approved Verb

Connect

Parameter Sets

Universal Auth

Connect-Infisical `
    -BaseUri <Uri> `
    -OrganizationId <string> `
    -ProjectId <string> `
    -Environment <string> `
    -ClientId <string> `
    -ClientSecret <SecureString> `
    [-SecretPath <string>] `
    [-ApiVersion <string>] `
    [-PassThru]

Token Auth

Connect-Infisical `
    -BaseUri <Uri> `
    -OrganizationId <string> `
    -ProjectId <string> `
    -Environment <string> `
    -AccessToken <SecureString> `
    [-SecretPath <string>] `
    [-ApiVersion <string>] `
    [-PassThru]

Defaults

SecretPath: /
ApiVersion: v4

Behavior

Validate BaseUri.
Validate ProjectId.
Validate Environment.
Validate OrganizationId when provided.
Validate ApiVersion.
Authenticate if using Universal Auth.
Store returned access token internally.
Make access token SecureString read-only.
Create InfisicalConnection.
Store connection in InfisicalSessionManager.
Return connection only when -PassThru is used.

Example

$ClientSecret = Read-Host -Prompt 'Client Secret' -AsSecureString

Connect-Infisical `
    -BaseUri 'https://app.infisical.com' `
    -OrganizationId 'organization-id' `
    -ProjectId 'project-id' `
    -Environment 'prod' `
    -ClientId 'client-id' `
    -ClientSecret $ClientSecret `
    -SecretPath '/' `
    -Verbose

Token Example

$Token = Read-Host -Prompt 'Access Token' -AsSecureString

Connect-Infisical `
    -BaseUri 'https://app.infisical.com' `
    -OrganizationId 'organization-id' `
    -ProjectId 'project-id' `
    -Environment 'prod' `
    -AccessToken $Token

14.2 Disconnect-Infisical

Purpose

Disconnect the current Infisical session.

Approved Verb

Disconnect

Parameters

Disconnect-Infisical [-PassThru]

Behavior

Clear current connection.
Dispose/clear token references where practical.
Clear cached authentication metadata.
Return nothing by default.
Return disconnected status object when -PassThru is used.

Example

Disconnect-Infisical -Verbose

14.3 Get-InfisicalSecrets

Purpose

Retrieve a list of secrets.

The Infisical list secrets endpoint supports listing from a base path and can recursively fetch subdirectories up to the documented depth limit. It also supports values such as viewSecretValue, expandSecretReferences, and recursive. (Infisical Blog)

Approved Verb

Get

Parameters

Get-InfisicalSecrets `
    [-ProjectId <string>] `
    [-Environment <string>] `
    [-SecretPath <string>] `
    [-Recursive] `
    [-IncludeImports] `
    [-IncludePersonalOverrides] `
    [-ExpandSecretReferences] `
    [-ViewSecretValue] `
    [-MetadataFilter <hashtable>] `
    [-TagSlugs <string[]>]

Defaults

ProjectId: Current connection ProjectId
Environment: Current connection Environment
SecretPath: Current connection DefaultSecretPath or /
Recursive: false
IncludeImports: false
ExpandSecretReferences: false
ViewSecretValue: true

Behavior

Require active connection.
Use explicit ProjectId/Environment/SecretPath when supplied.
Use connection defaults otherwise.
Build URI centrally.
Call ListSecrets endpoint.
Parse response into typed models.
Convert secretValue to read-only SecureString immediately.
Clear temporary response models where practical.
Return InfisicalSecret objects.

Example

Get-InfisicalSecrets -SecretPath '/production/web' -Recursive -Verbose

14.4 Get-InfisicalSecret

Purpose

Retrieve a single secret by name.

Infisical documents the retrieve-secret endpoint as /api/v4/secrets/{secretName}. (Infisical Blog)

Approved Verb

Get

Parameters

Get-InfisicalSecret `
    -SecretName <string> `
    [-ProjectId <string>] `
    [-Environment <string>] `
    [-SecretPath <string>] `
    [-Version <int>] `
    [-Type <InfisicalSecretType>] `
    [-ViewSecretValue] `
    [-ExpandSecretReferences] `
    [-IncludeImports]

Parameter Attributes

SecretName:
  Mandatory
  ValueFromPipelineByPropertyName

Defaults

ProjectId: Current connection ProjectId
Environment: Current connection Environment
SecretPath: Current connection DefaultSecretPath or /
Type: Shared
ViewSecretValue: true
ExpandSecretReferences: false
IncludeImports: false

Behavior

Require active connection.
Escape SecretName as path segment.
Build query centrally.
Call RetrieveSecret endpoint.
Parse response into typed model.
Convert secretValue to read-only SecureString immediately.
Return one InfisicalSecret object.

Example

Get-InfisicalSecret -SecretName 'SqlPassword' -SecretPath '/production/sql'

Pipeline example:

[pscustomobject]@{ SecretName = 'SqlPassword' } | Get-InfisicalSecret -SecretPath '/production/sql'

14.5 ConvertTo-InfisicalSecretDictionary

Purpose

Convert one or more InfisicalSecret objects to a case-insensitive dictionary.

Approved Verb

ConvertTo

Parameters

ConvertTo-InfisicalSecretDictionary `
    -InputObject <InfisicalSecret[]> `
    [-DuplicateKeyBehavior <Error|FirstWins|LastWins>]

Parameter Attributes

InputObject:
  Mandatory
  ValueFromPipeline

Default

DuplicateKeyBehavior: Error

Return Type

Dictionary<string, SecureString>

Comparer:

StringComparer.OrdinalIgnoreCase

Behavior

Key = SecretName.
Value = SecretValue as SecureString.
Do not convert values to plaintext.
Throw on duplicate keys by default.
Support FirstWins and LastWins when explicitly selected.

Example

$Secrets = Get-InfisicalSecrets -SecretPath '/production'
$Dictionary = $Secrets | ConvertTo-InfisicalSecretDictionary

14.6 Export-InfisicalSecrets

Purpose

Export secrets to:

JSON
YAML
ENV
XML
EnvironmentVariables

Approved Verb

Export

Parameters

Export-InfisicalSecrets `
    -InputObject <InfisicalSecret[]> `
    -Format <Json|Yaml|Env|Xml|EnvironmentVariables> `
    [-Path <FileInfo>] `
    [-Scope <Process|User|Machine>] `
    [-Force] `
    [-Encoding <UTF8|UTF8Bom|Unicode>]

Parameter Rules

InputObject:
  Mandatory
  ValueFromPipeline

Format:
  Mandatory

Path:
  Mandatory for Json, Yaml, Env, Xml
  Not required for EnvironmentVariables

Scope:
  Only used for EnvironmentVariables
  Default: Process

Encoding:
  Default: UTF8

Behavior

Collect pipeline input.
Use centralized exporter provider.
Create Path.Directory automatically when missing.
Use FileInfo.Directory directly.
Do not emit warning messages.
Do not log exported values.
Do not write secret plaintext to verbose output.
Use InfisicalSecret.UsePlainTextValue() for scoped plaintext conversion.

Example

Get-InfisicalSecrets -SecretPath '/production' | Export-InfisicalSecrets -Format Json -Path '.\secrets.json'
Get-InfisicalSecrets -SecretPath '/production' | Export-InfisicalSecrets -Format Env -Path '.\secrets.env'
Get-InfisicalSecrets -SecretPath '/production' | Export-InfisicalSecrets -Format EnvironmentVariables -Scope Process

15. Output Models

15.1 InfisicalSecret

public sealed class InfisicalSecret
{
    public string Id { get; set; }
    public string InternalId { get; set; }
    public string Workspace { get; set; }
    public string Environment { get; set; }
    public int? Version { get; set; }
    public InfisicalSecretType Type { get; set; }
    public string SecretName { get; set; }
    public SecureString SecretValue { get; set; }
    public bool SecretValueHidden { get; set; }
    public string SecretPath { get; set; }
    public string SecretComment { get; set; }
    public DateTimeOffset? CreatedAtUtc { get; set; }
    public DateTimeOffset? UpdatedAtUtc { get; set; }
    public bool IsRotatedSecret { get; set; }
    public Guid? RotationId { get; set; }
    public InfisicalSecretTag[] Tags { get; set; }
    public InfisicalSecretMetadata[] SecretMetadata { get; set; }

    public T UsePlainTextValue<T>(Func<string, T> action)
    {
        return SecureStringUtility.UsePlainText(SecretValue, action);
    }

    public void UsePlainTextValue(Action<string> action)
    {
        SecureStringUtility.UsePlainText(SecretValue, plainText =>
        {
            action(plainText);
            return true;
        });
    }

    public override string ToString()
    {
        return SecretName;
    }
}

15.2 Rules

No PlainTextValue property.
No public getter that returns plaintext.
No secret value in ToString().
No secret value in default display formatting.
No secret value in logs.
Plaintext access only through scoped method.
Exporters must use UsePlainTextValue().

15.3 InfisicalSecretMetadata

public sealed class InfisicalSecretMetadata
{
    public string Key { get; set; }
    public string Value { get; set; }
}

Only unencrypted metadata should be exported.

15.4 InfisicalSecretTag

public sealed class InfisicalSecretTag
{
    public string Id { get; set; }
    public string Slug { get; set; }
    public string Name { get; set; }
    public string Color { get; set; }
}

15.5 Enums

public enum InfisicalSecretType
{
    Shared,
    Personal
}
public enum InfisicalExportFormat
{
    Json,
    Yaml,
    Env,
    Xml,
    EnvironmentVariables
}
public enum InfisicalDuplicateKeyBehavior
{
    Error,
    FirstWins,
    LastWins
}

16. Export Format Specifications

16.1 ENV

Format:

Key=Value

Example:

SqlServer=192.168.1.10
SqlUser=appuser
SqlPassword=ExamplePassword

16.2 XML

Required schema:

<Secrets>
  <Secret>
    <SecretName></SecretName>
    <SecretValue></SecretValue>
    <SecretPath></SecretPath>
    <SecretMetadata>
      <Metadata>
        <Key></Key>
        <Value></Value>
      </Metadata>
    </SecretMetadata>
  </Secret>
</Secrets>

16.3 JSON

JSON should be an array of secret objects.

Example:

[
  {
    "SecretName": "production",
    "SecretValue": "192.168.1.10",
    "SecretPath": "/servers",
    "SecretMetadata": {
      "Owner": "Infrastructure"
    }
  },
  {
    "SecretName": "staging",
    "SecretValue": "192.168.1.12",
    "SecretPath": "/servers",
    "SecretMetadata": {
      "Owner": "Infrastructure"
    }
  }
]

16.4 YAML

Required shape:

Secrets:
  - SecretName: production
    SecretValue: 192.168.1.10
    SecretPath: /servers
    SecretMetadata:
      Owner: Infrastructure
  - SecretName: staging
    SecretValue: 192.168.1.12
    SecretPath: /servers
    SecretMetadata:
      Owner: Infrastructure

16.5 EnvironmentVariables

Use:

Environment.SetEnvironmentVariable(name, value, target);

Supported scopes:

Process
User
Machine

Default:

Process

No warnings should be emitted.


17. SecureString Utility

Required utility:

public static class SecureStringUtility
{
    public static SecureString ToReadOnlySecureString(string value)
    {
        SecureString secureString = new SecureString();

        if (!string.IsNullOrEmpty(value))
        {
            foreach (char character in value)
            {
                secureString.AppendChar(character);
            }
        }

        secureString.MakeReadOnly();

        return secureString;
    }

    public static T UsePlainText<T>(SecureString secureString, Func<string, T> action)
    {
        if (secureString == null)
        {
            throw new ArgumentNullException(nameof(secureString));
        }

        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        IntPtr pointer = IntPtr.Zero;

        try
        {
            pointer = Marshal.SecureStringToBSTR(secureString);
            string plainText = Marshal.PtrToStringBSTR(pointer);
            return action(plainText);
        }
        finally
        {
            if (pointer != IntPtr.Zero)
            {
                Marshal.ZeroFreeBSTR(pointer);
            }
        }
    }
}

Rules:

Use SecureString for credentials.
Use SecureString for secret output values.
Call MakeReadOnly() after population.
Do not reuse mutable SecureString values unless necessary.
Do not expose SecureString internals.
Do not log conversion failures with secret content.

18. HTTP Client Design

18.1 Interface

public interface IInfisicalHttpClient
{
    InfisicalHttpResponse Send(InfisicalHttpRequest request);
}

18.2 Request Model

public sealed class InfisicalHttpRequest
{
    public string OperationName { get; set; }
    public string EndpointName { get; set; }
    public string Method { get; set; }
    public Uri Uri { get; set; }
    public Dictionary<string, string> Headers { get; set; }
    public string Body { get; set; }
    public bool ContainsSecretMaterialInRequest { get; set; }
    public bool ContainsSecretMaterialInResponse { get; set; }
}

18.3 Response Model

public sealed class InfisicalHttpResponse
{
    public int StatusCode { get; set; }
    public string ReasonPhrase { get; set; }
    public string Body { get; set; }
    public Dictionary<string, string> Headers { get; set; }

    public void Clear()
    {
        Body = null;
        Headers?.Clear();
    }
}

18.4 Rules

Use synchronous request execution.
Do not log Authorization header.
Do not log request body when ContainsSecretMaterialInRequest is true.
Do not log response body when ContainsSecretMaterialInResponse is true.
Clear response body as soon as parsed.

19. Serialization Design

Required serializers:

IInfisicalSerializer
JsonInfisicalSerializer
YamlInfisicalSerializer
XmlInfisicalSerializer
EnvInfisicalSerializer
EnvironmentVariableExporter

Recommended packages:

Newtonsoft.Json
YamlDotNet

Responsibilities:

Serialize strongly typed export models.
Deserialize API responses.
Preserve line/position details where available.
Avoid logging raw secret-bearing data.
Clear temporary DTOs where practical.

20. DTO Design

API DTOs should be separate from public output models.

Example:

InfisicalSecretResponseDto
InfisicalSecretListResponseDto
InfisicalUniversalAuthLoginRequestDto
InfisicalUniversalAuthLoginResponseDto

Rules:

DTOs may temporarily hold plaintext from API responses.
DTOs must not be returned publicly.
DTOs should be cleared/released after mapping.
Mapping must convert secretValue to read-only SecureString immediately.

21. Mapping Design

Required mapper:

InfisicalSecretMapper

Responsibilities:

Map secretKey to SecretName.
Map secretValue to SecureString.
Map secretPath.
Map environment.
Map metadata.
Map tags.
Map timestamps to UTC DateTimeOffset.
Clear DTO secretValue after mapping where practical.

Example mapping behavior:

DTO.secretKey -> InfisicalSecret.SecretName
DTO.secretValue -> InfisicalSecret.SecretValue
DTO.secretPath -> InfisicalSecret.SecretPath
DTO.secretMetadata -> InfisicalSecret.SecretMetadata

22. Documentation Requirements

Each public cmdlet must have help and examples.

Docs required:

about_PSInfisicalAPI.help.txt
Connect-Infisical.md
Disconnect-Infisical.md
Get-InfisicalSecrets.md
Get-InfisicalSecret.md
ConvertTo-InfisicalSecretDictionary.md
Export-InfisicalSecrets.md

Each cmdlet help page must include:

Synopsis
Description
Parameters
Inputs
Outputs
Examples
Notes

Public examples should be clean and natural.

Example:

Connect-Infisical -BaseUri 'https://app.infisical.com' -OrganizationId 'org-id' -ProjectId 'project-id' -Environment 'prod' -ClientId 'client-id' -ClientSecret $ClientSecret

Do not force examples to show internal FileInfo or Path.Combine usage.


23. Type and Format Data

The module should include formatting so secrets display safely.

Default view for InfisicalSecret:

SecretName
SecretPath
Environment
Type
Version
UpdatedAtUtc
SecretValueHidden

Do not display:

SecretValue
AccessToken
ClientSecret
RawApiResponse

24. Testing Requirements

24.1 Unit Tests

Minimum tests:

Build script is idempotent.
Manifest version matches assembly version.
Manifest commit hash exists.
PSM1 imports binary from bin folder.
Endpoint registry returns ListSecrets endpoint.
Endpoint registry returns RetrieveSecret endpoint.
Endpoint registry returns UniversalAuthLogin endpoint.
URI builder escapes query parameters.
URI builder escapes secret name path segment.
Logger does not log secret values.
Logger formats UTC timestamp correctly.
Error handler preserves HTTP status code.
Error handler sanitizes secret-bearing response content.
SecureStringUtility creates read-only SecureString.
InfisicalSecret.ToString() returns SecretName only.
InfisicalSecret.UsePlainTextValue() scopes plaintext access.
Get-InfisicalSecrets maps secretKey to SecretName.
Get-InfisicalSecrets maps secretValue to SecureString.
ConvertTo-InfisicalSecretDictionary uses OrdinalIgnoreCase.
ConvertTo-InfisicalSecretDictionary throws on duplicate by default.
Export JSON creates directory automatically.
Export YAML creates directory automatically.
Export XML matches required schema.
Export ENV writes Key=Value.
EnvironmentVariables export defaults to Process.
No warning emitted during export.

24.2 Integration Tests

Integration tests require:

CLOUDINIT_INFISICAL_APIURL
CLOUDINIT_INFISICAL_ORGANIZATIONID
CLOUDINIT_INFISICAL_PROJECTID
CLOUDINIT_INFISICAL_ENVIRONMENT
CLOUDINIT_INFISICAL_CLIENTID
CLOUDINIT_INFISICAL_CLIENTSECRET

Integration tests must verify:

Connect-Infisical works with Universal Auth.
Get-InfisicalSecrets returns typed objects.
Get-InfisicalSecret returns one typed object.
SecretValue is SecureString.
SecretValue is read-only.
Export formats complete without warning.
Disconnect-Infisical clears current session.

25. Implementation Milestones

Milestone 1: Skeleton

Repository structure
C# project
Test project
Initial build.ps1
Initial PSD1 generation
Initial PSM1 generation
Version generation
Commit hash embedding
CHANGELOG.md

Milestone 2: Core Infrastructure

Central logger
Central error types
Central error handler
SecureString utility
Path utility
Endpoint registry
URI builder
Sanitizer

Milestone 3: HTTP and Serialization

Synchronous HTTP client
HTTP request/response models
JSON serializer
YAML serializer
XML serializer
ENV serializer
Response clearing behavior

Milestone 4: Connection and Auth

Connection model
Session manager
Auth provider interface
Universal Auth provider
Token Auth provider
Connect-Infisical
Disconnect-Infisical

Milestone 5: Secrets

Secret DTOs
Secret output models
Secret mapper
Get-InfisicalSecrets
Get-InfisicalSecret
Safe formatting

Milestone 6: Conversion and Export

ConvertTo-InfisicalSecretDictionary
JSON export
YAML export
XML export
ENV export
EnvironmentVariables export
No-warning export behavior

Milestone 7: Tests and Docs

Unit tests
Integration test harness
External help
README examples
CHANGELOG update
Import validation in Windows PowerShell 5.1
Import validation in PowerShell 7+

26. Acceptance Criteria

The project is acceptable only when all of these are true:

The module is named PSInfisicalAPI.
The module is C# based.
The module targets .NET Standard 2.0.
The module works in Windows PowerShell 5.1.
The module works in PowerShell 7+.
No async keyword exists in source.
No await keyword exists in source.
All public cmdlets use approved verbs.
All public cmdlets have help.
All public cmdlets have examples.
Connect-Infisical supports Universal Auth.
Connect-Infisical supports Token Auth.
Disconnect-Infisical clears the current connection.
Other cmdlets automatically use the current connection.
Endpoint paths are centralized.
URI building is centralized.
Query construction is centralized.
All URLs use System.Uri or UriBuilder.
All internal paths use FileInfo, DirectoryInfo, and Path.Combine.
Logging is centralized.
Logging uses [UTC Timestamp] - [Level] - [Component] - Message.
Operations log before and after.
Failures log sanitized error detail.
Secret values are never logged.
Client secrets are never logged.
Access tokens are never logged.
Authorization headers are never logged.
Secret API response bodies are never logged.
Public cmdlets never return raw API responses.
Secret values are stored as SecureString.
SecureString values are made read-only where practical.
Secret object has scoped plaintext conversion method.
Secret object has no plaintext property.
Exporters use scoped plaintext conversion.
Export operations do not emit warning messages.
Export JSON works.
Export YAML works.
Export ENV works.
Export XML works.
Export EnvironmentVariables works.
EnvironmentVariables export supports Process, User, and Machine.
EnvironmentVariables export defaults to Process.
Export path uses FileInfo.
Export creates missing directories.
ConvertTo-InfisicalSecretDictionary returns case-insensitive dictionary.
Duplicate dictionary keys error by default.
Version format is yyyy.MM.dd.HHmm.
Commit hash is embedded.
Build script is idempotent.
Build script generates manifest.
Build script copies binaries to Module/PSInfisicalAPI/bin.
Build script creates release folders.
Build script can run unit tests.
Build script can run integration tests only when explicitly requested.
Integration tests read machine-scope CLOUDINIT_INFISICAL_* variables.
Successful milestones are committed when -CommitOnSuccess is used.
Failed builds are never committed.

27. Final Design Principle

PSInfisicalAPI should not be a one-off secrets script wrapped in a module. It should be a reusable, strongly typed, secure framework for Infisical API interaction, with secrets as the first supported resource family and future support for certificates, identities, projects, folders, imports, rotations, and other Infisical API areas added cleanly through new endpoint definitions, models, services, and cmdlets.