# 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: ```powershell Connect-Infisical Disconnect-Infisical Get-InfisicalSecrets Get-InfisicalSecret ConvertTo-InfisicalSecretDictionary Export-InfisicalSecrets ``` Infisical’s 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][1]) --- # 2. Non-Negotiable Requirements ## 2.1 Runtime The module must target: ```text .NET Standard 2.0 PowerShellStandard.Library Windows PowerShell 5.1 PowerShell 7+ ``` ## 2.2 No Async/Await The codebase must contain **no** usage of: ```csharp 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: ```text 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: ```text 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: ```text JSON YAML ENV XML EnvironmentVariables ``` --- # 3. Repository Structure The repository must follow this structure: ```text 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: ```text 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: ```powershell @{ RootModule = 'PSInfisicalAPI.psm1' ModuleVersion = 'yyyy.MM.dd.HHmm' 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 = '' } } } ``` ## 4.2 PSM1 The `.psm1` must be minimal and only load the binary module plus format/type data. ```powershell $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: ```text yyyy.MM.dd.HHmm ``` Example: ```text 2026.06.02.2140 ``` The version must be generated once per build and applied consistently to: ```text PSD1 ModuleVersion AssemblyVersion AssemblyFileVersion AssemblyInformationalVersion Release folder CHANGELOG.md Generated docs if applicable ``` The git commit hash must be embedded separately: ```text PSD1 PrivateData.PSData.CommitHash AssemblyMetadata("CommitHash", "") AssemblyInformationalVersion = "yyyy.MM.dd.HHmm" ``` --- # 6. Build Script Specification The build script must be: ```text 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 ```powershell 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 ```text 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 ```powershell .\build.ps1 -Clean -Restore -RunTests -CreateRelease -CommitOnSuccess ``` ```powershell .\build.ps1 -Clean -Restore -RunTests -RunIntegrationTests -CreateRelease ``` ## 6.4 Periodic Build and Commit Rule After each logical milestone: ```text Build. Test. If successful, commit. If failed, do not commit. ``` Suggested commit messages: ```text 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: ```powershell [System.EnvironmentVariableTarget]::Machine ``` Required variables: ```text CLOUDINIT_INFISICAL_APIURL CLOUDINIT_INFISICAL_ORGANIZATIONID CLOUDINIT_INFISICAL_PROJECTID CLOUDINIT_INFISICAL_ENVIRONMENT CLOUDINIT_INFISICAL_CLIENTID CLOUDINIT_INFISICAL_CLIENTSECRET ``` ## 7.1 Integration Test Rules ```text 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: ```csharp 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: ```text [UTC Timestamp] - [Level] - [Component] - Message ``` Example: ```text [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 ```text 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: ```text Attempting to ... ... was successful. ... failed. ``` Use `Please Wait...` where appropriate. Examples: ```text 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: ```text 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: ```text InfisicalException InfisicalApiException InfisicalAuthenticationException InfisicalHttpException InfisicalSerializationException InfisicalExportException InfisicalConfigurationException InfisicalErrorDetails InfisicalErrorHandler ``` ## 9.1 Error Details Errors should preserve: ```text 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: ```text [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: [UTC] - [Error] - [ErrorHandler] - Line: [UTC] - [Error] - [ErrorHandler] - Position: ``` ## 9.3 PowerShell Error Records Cmdlets must emit proper `ErrorRecord` objects. Examples: ```text 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 ```csharp 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 ```text 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][2]) ## 10.3 API Version Flexibility `Connect-Infisical` should accept: ```powershell -ApiVersion ``` Default: ```text 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: ```csharp System.Uri System.UriBuilder ``` URI construction must be centralized in: ```text InfisicalUriBuilder ``` Responsibilities: ```text 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: ```csharp System.IO.FileInfo System.IO.DirectoryInfo System.IO.Path.Combine(...) ``` PowerShell build/helper scripts must use: ```powershell [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: ```powershell 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: ```text 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][3]) ## 12.2 Future Auth Types Design must allow future support for: ```text 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 ```csharp public interface IInfisicalAuthProvider { string Name { get; } InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger); } ``` ## 12.4 Authentication Result ```csharp 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 ```csharp 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 ```csharp 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 ```text Connect ``` ## Parameter Sets ### Universal Auth ```powershell Connect-Infisical ` -BaseUri ` -OrganizationId ` -ProjectId ` -Environment ` -ClientId ` -ClientSecret ` [-SecretPath ] ` [-ApiVersion ] ` [-PassThru] ``` ### Token Auth ```powershell Connect-Infisical ` -BaseUri ` -OrganizationId ` -ProjectId ` -Environment ` -AccessToken ` [-SecretPath ] ` [-ApiVersion ] ` [-PassThru] ``` ## Defaults ```text SecretPath: / ApiVersion: v4 ``` ## Behavior ```text 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 ```powershell $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 ```powershell $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 ```text Disconnect ``` ## Parameters ```powershell Disconnect-Infisical [-PassThru] ``` ## Behavior ```text 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 ```powershell 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][4]) ## Approved Verb ```text Get ``` ## Parameters ```powershell Get-InfisicalSecrets ` [-ProjectId ] ` [-Environment ] ` [-SecretPath ] ` [-Recursive] ` [-IncludeImports] ` [-IncludePersonalOverrides] ` [-ExpandSecretReferences] ` [-ViewSecretValue] ` [-MetadataFilter ] ` [-TagSlugs ] ``` ## Defaults ```text ProjectId: Current connection ProjectId Environment: Current connection Environment SecretPath: Current connection DefaultSecretPath or / Recursive: false IncludeImports: false ExpandSecretReferences: false ViewSecretValue: false ``` ## Behavior ```text 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 ```powershell 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][5]) ## Approved Verb ```text Get ``` ## Parameters ```powershell Get-InfisicalSecret ` -SecretName ` [-ProjectId ] ` [-Environment ] ` [-SecretPath ] ` [-Version ] ` [-Type ] ` [-ViewSecretValue] ` [-ExpandSecretReferences] ` [-IncludeImports] ``` ## Parameter Attributes ```text SecretName: Mandatory ValueFromPipelineByPropertyName ``` ## Defaults ```text ProjectId: Current connection ProjectId Environment: Current connection Environment SecretPath: Current connection DefaultSecretPath or / Type: Shared ViewSecretValue: false ExpandSecretReferences: false IncludeImports: false ``` ## Behavior ```text 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 ```powershell Get-InfisicalSecret -SecretName 'SqlPassword' -SecretPath '/production/sql' ``` Pipeline example: ```powershell [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 ```text ConvertTo ``` ## Parameters ```powershell ConvertTo-InfisicalSecretDictionary ` -InputObject ` [-DuplicateKeyBehavior ] ``` ## Parameter Attributes ```text InputObject: Mandatory ValueFromPipeline ``` ## Default ```text DuplicateKeyBehavior: Error ``` ## Return Type ```csharp Dictionary ``` Comparer: ```csharp StringComparer.OrdinalIgnoreCase ``` ## Behavior ```text 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 ```powershell $Secrets = Get-InfisicalSecrets -SecretPath '/production' $Dictionary = $Secrets | ConvertTo-InfisicalSecretDictionary ``` --- # 14.6 Export-InfisicalSecrets ## Purpose Export secrets to: ```text JSON YAML ENV XML EnvironmentVariables ``` ## Approved Verb ```text Export ``` ## Parameters ```powershell Export-InfisicalSecrets ` -InputObject ` -Format ` [-Path ] ` [-Scope ] ` [-Force] ` [-Encoding ] ``` ## Parameter Rules ```text 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 ```text 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 ```powershell Get-InfisicalSecrets -SecretPath '/production' | Export-InfisicalSecrets -Format Json -Path '.\secrets.json' ``` ```powershell Get-InfisicalSecrets -SecretPath '/production' | Export-InfisicalSecrets -Format Env -Path '.\secrets.env' ``` ```powershell Get-InfisicalSecrets -SecretPath '/production' | Export-InfisicalSecrets -Format EnvironmentVariables -Scope Process ``` --- # 15. Output Models ## 15.1 InfisicalSecret ```csharp 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(Func action) { return SecureStringUtility.UsePlainText(SecretValue, action); } public void UsePlainTextValue(Action action) { SecureStringUtility.UsePlainText(SecretValue, plainText => { action(plainText); return true; }); } public override string ToString() { return SecretName; } } ``` ## 15.2 Rules ```text 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 ```csharp public sealed class InfisicalSecretMetadata { public string Key { get; set; } public string Value { get; set; } } ``` Only unencrypted metadata should be exported. ## 15.4 InfisicalSecretTag ```csharp 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 ```csharp public enum InfisicalSecretType { Shared, Personal } ``` ```csharp public enum InfisicalExportFormat { Json, Yaml, Env, Xml, EnvironmentVariables } ``` ```csharp public enum InfisicalDuplicateKeyBehavior { Error, FirstWins, LastWins } ``` --- # 16. Export Format Specifications ## 16.1 ENV Format: ```text Key=Value ``` Example: ```text SqlServer=192.168.1.10 SqlUser=appuser SqlPassword=ExamplePassword ``` ## 16.2 XML Required schema: ```xml ``` ## 16.3 JSON JSON should be an array of secret objects. Example: ```json [ { "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: ```yaml 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: ```csharp Environment.SetEnvironmentVariable(name, value, target); ``` Supported scopes: ```text Process User Machine ``` Default: ```text Process ``` No warnings should be emitted. --- # 17. SecureString Utility Required utility: ```csharp 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(SecureString secureString, Func 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: ```text 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 ```csharp public interface IInfisicalHttpClient { InfisicalHttpResponse Send(InfisicalHttpRequest request); } ``` ## 18.2 Request Model ```csharp 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 Headers { get; set; } public string Body { get; set; } public bool ContainsSecretMaterialInRequest { get; set; } public bool ContainsSecretMaterialInResponse { get; set; } } ``` ## 18.3 Response Model ```csharp public sealed class InfisicalHttpResponse { public int StatusCode { get; set; } public string ReasonPhrase { get; set; } public string Body { get; set; } public Dictionary Headers { get; set; } public void Clear() { Body = null; Headers?.Clear(); } } ``` ## 18.4 Rules ```text 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: ```text IInfisicalSerializer JsonInfisicalSerializer YamlInfisicalSerializer XmlInfisicalSerializer EnvInfisicalSerializer EnvironmentVariableExporter ``` Recommended packages: ```text Newtonsoft.Json YamlDotNet ``` Responsibilities: ```text 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: ```text InfisicalSecretResponseDto InfisicalSecretListResponseDto InfisicalUniversalAuthLoginRequestDto InfisicalUniversalAuthLoginResponseDto ``` Rules: ```text 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: ```text InfisicalSecretMapper ``` Responsibilities: ```text 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: ```text 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: ```text 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: ```text Synopsis Description Parameters Inputs Outputs Examples Notes ``` Public examples should be clean and natural. Example: ```powershell 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`: ```text SecretName SecretPath Environment Type Version UpdatedAtUtc SecretValueHidden ``` Do not display: ```text SecretValue AccessToken ClientSecret RawApiResponse ``` --- # 24. Testing Requirements ## 24.1 Unit Tests Minimum tests: ```text 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: ```text CLOUDINIT_INFISICAL_APIURL CLOUDINIT_INFISICAL_ORGANIZATIONID CLOUDINIT_INFISICAL_PROJECTID CLOUDINIT_INFISICAL_ENVIRONMENT CLOUDINIT_INFISICAL_CLIENTID CLOUDINIT_INFISICAL_CLIENTSECRET ``` Integration tests must verify: ```text 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 ```text 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 ```text Central logger Central error types Central error handler SecureString utility Path utility Endpoint registry URI builder Sanitizer ``` ## Milestone 3: HTTP and Serialization ```text Synchronous HTTP client HTTP request/response models JSON serializer YAML serializer XML serializer ENV serializer Response clearing behavior ``` ## Milestone 4: Connection and Auth ```text Connection model Session manager Auth provider interface Universal Auth provider Token Auth provider Connect-Infisical Disconnect-Infisical ``` ## Milestone 5: Secrets ```text Secret DTOs Secret output models Secret mapper Get-InfisicalSecrets Get-InfisicalSecret Safe formatting ``` ## Milestone 6: Conversion and Export ```text ConvertTo-InfisicalSecretDictionary JSON export YAML export XML export ENV export EnvironmentVariables export No-warning export behavior ``` ## Milestone 7: Tests and Docs ```text 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: ```text 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. [1]: https://infisical.com/docs/api-reference/overview/introduction?utm_source=chatgpt.com "API Reference" [2]: https://infisical.com/docs/documentation/platform/identities/universal-auth?utm_source=chatgpt.com "Universal Auth" [3]: https://infisical.com/docs/api-reference/overview/authentication?utm_source=chatgpt.com "Authentication" [4]: https://infisical.com/docs/api-reference/endpoints/secrets/list?utm_source=chatgpt.com "List secrets" [5]: https://infisical.com/docs/api-reference/endpoints/secrets/read?utm_source=chatgpt.com "Retrieve"