@attest-it/core 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -637,6 +637,522 @@ declare function resolveConfigPaths(config: Config, repoRoot: string): Config;
637
637
  */
638
638
  declare function toAttestItConfig(config: Config): AttestItConfig;
639
639
 
640
+ /**
641
+ * Policy configuration schema and validation for attest-it.
642
+ *
643
+ * Policy files contain trust and security-critical configuration that should
644
+ * be loaded from the default branch to prevent tampering by PR authors.
645
+ *
646
+ * ============================================================================
647
+ * IMPORTANT: DOCUMENTATION SYNC REQUIRED
648
+ * ============================================================================
649
+ * When modifying any schema in this file, you MUST also update:
650
+ *
651
+ * 1. README.md - Update the "Configuration" section's quick example
652
+ * 2. docs/configuration.md - Update the comprehensive configuration reference
653
+ * 3. schemas/policy.schema.json - Run `pnpm --filter @attest-it/core generate:schemas`
654
+ *
655
+ * The configuration format is a key part of the user experience. Any schema
656
+ * changes should be reflected in user-facing documentation.
657
+ * ============================================================================
658
+ */
659
+
660
+ /**
661
+ * Zod schema for the policy configuration file.
662
+ *
663
+ * Policy files define:
664
+ * - Security settings (key paths, attestation storage, max age)
665
+ * - Team members and their public keys
666
+ * - Gates with authorization rules
667
+ *
668
+ * @public
669
+ */
670
+ declare const policySchema: z.ZodObject<{
671
+ version: z.ZodLiteral<1>;
672
+ settings: z.ZodDefault<z.ZodObject<{
673
+ maxAgeDays: z.ZodDefault<z.ZodNumber>;
674
+ publicKeyPath: z.ZodDefault<z.ZodString>;
675
+ attestationsPath: z.ZodDefault<z.ZodString>;
676
+ }, "strict", z.ZodTypeAny, {
677
+ maxAgeDays: number;
678
+ publicKeyPath: string;
679
+ attestationsPath: string;
680
+ }, {
681
+ maxAgeDays?: number | undefined;
682
+ publicKeyPath?: string | undefined;
683
+ attestationsPath?: string | undefined;
684
+ }>>;
685
+ team: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
686
+ name: z.ZodString;
687
+ email: z.ZodOptional<z.ZodString>;
688
+ github: z.ZodOptional<z.ZodString>;
689
+ publicKey: z.ZodString;
690
+ }, "strict", z.ZodTypeAny, {
691
+ name: string;
692
+ publicKey: string;
693
+ email?: string | undefined;
694
+ github?: string | undefined;
695
+ }, {
696
+ name: string;
697
+ publicKey: string;
698
+ email?: string | undefined;
699
+ github?: string | undefined;
700
+ }>>>;
701
+ gates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
702
+ name: z.ZodString;
703
+ description: z.ZodString;
704
+ authorizedSigners: z.ZodArray<z.ZodString, "many">;
705
+ fingerprint: z.ZodObject<{
706
+ paths: z.ZodArray<z.ZodString, "many">;
707
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
708
+ }, "strict", z.ZodTypeAny, {
709
+ paths: string[];
710
+ exclude?: string[] | undefined;
711
+ }, {
712
+ paths: string[];
713
+ exclude?: string[] | undefined;
714
+ }>;
715
+ maxAge: z.ZodEffects<z.ZodString, string, string>;
716
+ }, "strict", z.ZodTypeAny, {
717
+ name: string;
718
+ description: string;
719
+ authorizedSigners: string[];
720
+ fingerprint: {
721
+ paths: string[];
722
+ exclude?: string[] | undefined;
723
+ };
724
+ maxAge: string;
725
+ }, {
726
+ name: string;
727
+ description: string;
728
+ authorizedSigners: string[];
729
+ fingerprint: {
730
+ paths: string[];
731
+ exclude?: string[] | undefined;
732
+ };
733
+ maxAge: string;
734
+ }>>>;
735
+ }, "strict", z.ZodTypeAny, {
736
+ version: 1;
737
+ settings: {
738
+ maxAgeDays: number;
739
+ publicKeyPath: string;
740
+ attestationsPath: string;
741
+ };
742
+ team?: Record<string, {
743
+ name: string;
744
+ publicKey: string;
745
+ email?: string | undefined;
746
+ github?: string | undefined;
747
+ }> | undefined;
748
+ gates?: Record<string, {
749
+ name: string;
750
+ description: string;
751
+ authorizedSigners: string[];
752
+ fingerprint: {
753
+ paths: string[];
754
+ exclude?: string[] | undefined;
755
+ };
756
+ maxAge: string;
757
+ }> | undefined;
758
+ }, {
759
+ version: 1;
760
+ settings?: {
761
+ maxAgeDays?: number | undefined;
762
+ publicKeyPath?: string | undefined;
763
+ attestationsPath?: string | undefined;
764
+ } | undefined;
765
+ team?: Record<string, {
766
+ name: string;
767
+ publicKey: string;
768
+ email?: string | undefined;
769
+ github?: string | undefined;
770
+ }> | undefined;
771
+ gates?: Record<string, {
772
+ name: string;
773
+ description: string;
774
+ authorizedSigners: string[];
775
+ fingerprint: {
776
+ paths: string[];
777
+ exclude?: string[] | undefined;
778
+ };
779
+ maxAge: string;
780
+ }> | undefined;
781
+ }>;
782
+ /**
783
+ * Policy configuration type inferred from the Zod schema.
784
+ * @public
785
+ */
786
+ type PolicyConfig = z.infer<typeof policySchema>;
787
+ /**
788
+ * Error thrown when policy configuration is invalid.
789
+ * @public
790
+ */
791
+ declare class PolicyValidationError extends Error {
792
+ readonly issues: z.ZodIssue[];
793
+ constructor(message: string, issues: z.ZodIssue[]);
794
+ }
795
+ /**
796
+ * Parse policy configuration content from a string.
797
+ *
798
+ * @param content - The policy file content
799
+ * @param format - The format of the content ('yaml' or 'json')
800
+ * @returns Parsed and validated policy configuration
801
+ * @throws {@link PolicyValidationError} If validation fails
802
+ * @public
803
+ */
804
+ declare function parsePolicyContent(content: string, format: 'yaml' | 'json'): PolicyConfig;
805
+
806
+ /**
807
+ * Operational configuration schema and validation for attest-it.
808
+ *
809
+ * Operational files contain non-security-critical configuration that can
810
+ * be loaded from PR branches (e.g., suite definitions, command execution settings).
811
+ *
812
+ * ============================================================================
813
+ * IMPORTANT: DOCUMENTATION SYNC REQUIRED
814
+ * ============================================================================
815
+ * When modifying any schema in this file, you MUST also update:
816
+ *
817
+ * 1. README.md - Update the "Configuration" section's quick example
818
+ * 2. docs/configuration.md - Update the comprehensive configuration reference
819
+ * 3. schemas/config.schema.json - Run `pnpm --filter @attest-it/core generate:schemas`
820
+ *
821
+ * The configuration format is a key part of the user experience. Any schema
822
+ * changes should be reflected in user-facing documentation.
823
+ * ============================================================================
824
+ */
825
+
826
+ /**
827
+ * Zod schema for the operational configuration file.
828
+ *
829
+ * Operational files define:
830
+ * - Command execution settings (default command, key provider)
831
+ * - Suite definitions with command execution details
832
+ * - Suite groups for organizational purposes
833
+ *
834
+ * @public
835
+ */
836
+ declare const operationalSchema: z.ZodObject<{
837
+ version: z.ZodLiteral<1>;
838
+ settings: z.ZodDefault<z.ZodObject<{
839
+ defaultCommand: z.ZodOptional<z.ZodString>;
840
+ keyProvider: z.ZodOptional<z.ZodObject<{
841
+ type: z.ZodUnion<[z.ZodEnum<["filesystem", "1password"]>, z.ZodString]>;
842
+ options: z.ZodOptional<z.ZodObject<{
843
+ privateKeyPath: z.ZodOptional<z.ZodString>;
844
+ account: z.ZodOptional<z.ZodString>;
845
+ vault: z.ZodOptional<z.ZodString>;
846
+ itemName: z.ZodOptional<z.ZodString>;
847
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
848
+ privateKeyPath: z.ZodOptional<z.ZodString>;
849
+ account: z.ZodOptional<z.ZodString>;
850
+ vault: z.ZodOptional<z.ZodString>;
851
+ itemName: z.ZodOptional<z.ZodString>;
852
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
853
+ privateKeyPath: z.ZodOptional<z.ZodString>;
854
+ account: z.ZodOptional<z.ZodString>;
855
+ vault: z.ZodOptional<z.ZodString>;
856
+ itemName: z.ZodOptional<z.ZodString>;
857
+ }, z.ZodTypeAny, "passthrough">>>;
858
+ }, "strict", z.ZodTypeAny, {
859
+ type: string;
860
+ options?: z.objectOutputType<{
861
+ privateKeyPath: z.ZodOptional<z.ZodString>;
862
+ account: z.ZodOptional<z.ZodString>;
863
+ vault: z.ZodOptional<z.ZodString>;
864
+ itemName: z.ZodOptional<z.ZodString>;
865
+ }, z.ZodTypeAny, "passthrough"> | undefined;
866
+ }, {
867
+ type: string;
868
+ options?: z.objectInputType<{
869
+ privateKeyPath: z.ZodOptional<z.ZodString>;
870
+ account: z.ZodOptional<z.ZodString>;
871
+ vault: z.ZodOptional<z.ZodString>;
872
+ itemName: z.ZodOptional<z.ZodString>;
873
+ }, z.ZodTypeAny, "passthrough"> | undefined;
874
+ }>>;
875
+ }, "strict", z.ZodTypeAny, {
876
+ defaultCommand?: string | undefined;
877
+ keyProvider?: {
878
+ type: string;
879
+ options?: z.objectOutputType<{
880
+ privateKeyPath: z.ZodOptional<z.ZodString>;
881
+ account: z.ZodOptional<z.ZodString>;
882
+ vault: z.ZodOptional<z.ZodString>;
883
+ itemName: z.ZodOptional<z.ZodString>;
884
+ }, z.ZodTypeAny, "passthrough"> | undefined;
885
+ } | undefined;
886
+ }, {
887
+ defaultCommand?: string | undefined;
888
+ keyProvider?: {
889
+ type: string;
890
+ options?: z.objectInputType<{
891
+ privateKeyPath: z.ZodOptional<z.ZodString>;
892
+ account: z.ZodOptional<z.ZodString>;
893
+ vault: z.ZodOptional<z.ZodString>;
894
+ itemName: z.ZodOptional<z.ZodString>;
895
+ }, z.ZodTypeAny, "passthrough"> | undefined;
896
+ } | undefined;
897
+ }>>;
898
+ suites: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodObject<{
899
+ gate: z.ZodOptional<z.ZodString>;
900
+ description: z.ZodOptional<z.ZodString>;
901
+ packages: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
902
+ files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
903
+ ignore: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
904
+ command: z.ZodOptional<z.ZodString>;
905
+ timeout: z.ZodOptional<z.ZodString>;
906
+ interactive: z.ZodOptional<z.ZodBoolean>;
907
+ invalidates: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
908
+ depends_on: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
909
+ }, "strict", z.ZodTypeAny, {
910
+ description?: string | undefined;
911
+ gate?: string | undefined;
912
+ packages?: string[] | undefined;
913
+ files?: string[] | undefined;
914
+ ignore?: string[] | undefined;
915
+ command?: string | undefined;
916
+ timeout?: string | undefined;
917
+ interactive?: boolean | undefined;
918
+ invalidates?: string[] | undefined;
919
+ depends_on?: string[] | undefined;
920
+ }, {
921
+ description?: string | undefined;
922
+ gate?: string | undefined;
923
+ packages?: string[] | undefined;
924
+ files?: string[] | undefined;
925
+ ignore?: string[] | undefined;
926
+ command?: string | undefined;
927
+ timeout?: string | undefined;
928
+ interactive?: boolean | undefined;
929
+ invalidates?: string[] | undefined;
930
+ depends_on?: string[] | undefined;
931
+ }>, {
932
+ description?: string | undefined;
933
+ gate?: string | undefined;
934
+ packages?: string[] | undefined;
935
+ files?: string[] | undefined;
936
+ ignore?: string[] | undefined;
937
+ command?: string | undefined;
938
+ timeout?: string | undefined;
939
+ interactive?: boolean | undefined;
940
+ invalidates?: string[] | undefined;
941
+ depends_on?: string[] | undefined;
942
+ }, {
943
+ description?: string | undefined;
944
+ gate?: string | undefined;
945
+ packages?: string[] | undefined;
946
+ files?: string[] | undefined;
947
+ ignore?: string[] | undefined;
948
+ command?: string | undefined;
949
+ timeout?: string | undefined;
950
+ interactive?: boolean | undefined;
951
+ invalidates?: string[] | undefined;
952
+ depends_on?: string[] | undefined;
953
+ }>>, Record<string, {
954
+ description?: string | undefined;
955
+ gate?: string | undefined;
956
+ packages?: string[] | undefined;
957
+ files?: string[] | undefined;
958
+ ignore?: string[] | undefined;
959
+ command?: string | undefined;
960
+ timeout?: string | undefined;
961
+ interactive?: boolean | undefined;
962
+ invalidates?: string[] | undefined;
963
+ depends_on?: string[] | undefined;
964
+ }>, Record<string, {
965
+ description?: string | undefined;
966
+ gate?: string | undefined;
967
+ packages?: string[] | undefined;
968
+ files?: string[] | undefined;
969
+ ignore?: string[] | undefined;
970
+ command?: string | undefined;
971
+ timeout?: string | undefined;
972
+ interactive?: boolean | undefined;
973
+ invalidates?: string[] | undefined;
974
+ depends_on?: string[] | undefined;
975
+ }>>;
976
+ groups: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
977
+ }, "strict", z.ZodTypeAny, {
978
+ version: 1;
979
+ settings: {
980
+ defaultCommand?: string | undefined;
981
+ keyProvider?: {
982
+ type: string;
983
+ options?: z.objectOutputType<{
984
+ privateKeyPath: z.ZodOptional<z.ZodString>;
985
+ account: z.ZodOptional<z.ZodString>;
986
+ vault: z.ZodOptional<z.ZodString>;
987
+ itemName: z.ZodOptional<z.ZodString>;
988
+ }, z.ZodTypeAny, "passthrough"> | undefined;
989
+ } | undefined;
990
+ };
991
+ suites: Record<string, {
992
+ description?: string | undefined;
993
+ gate?: string | undefined;
994
+ packages?: string[] | undefined;
995
+ files?: string[] | undefined;
996
+ ignore?: string[] | undefined;
997
+ command?: string | undefined;
998
+ timeout?: string | undefined;
999
+ interactive?: boolean | undefined;
1000
+ invalidates?: string[] | undefined;
1001
+ depends_on?: string[] | undefined;
1002
+ }>;
1003
+ groups?: Record<string, string[]> | undefined;
1004
+ }, {
1005
+ version: 1;
1006
+ suites: Record<string, {
1007
+ description?: string | undefined;
1008
+ gate?: string | undefined;
1009
+ packages?: string[] | undefined;
1010
+ files?: string[] | undefined;
1011
+ ignore?: string[] | undefined;
1012
+ command?: string | undefined;
1013
+ timeout?: string | undefined;
1014
+ interactive?: boolean | undefined;
1015
+ invalidates?: string[] | undefined;
1016
+ depends_on?: string[] | undefined;
1017
+ }>;
1018
+ settings?: {
1019
+ defaultCommand?: string | undefined;
1020
+ keyProvider?: {
1021
+ type: string;
1022
+ options?: z.objectInputType<{
1023
+ privateKeyPath: z.ZodOptional<z.ZodString>;
1024
+ account: z.ZodOptional<z.ZodString>;
1025
+ vault: z.ZodOptional<z.ZodString>;
1026
+ itemName: z.ZodOptional<z.ZodString>;
1027
+ }, z.ZodTypeAny, "passthrough"> | undefined;
1028
+ } | undefined;
1029
+ } | undefined;
1030
+ groups?: Record<string, string[]> | undefined;
1031
+ }>;
1032
+ /**
1033
+ * Operational configuration type inferred from the Zod schema.
1034
+ * @public
1035
+ */
1036
+ type OperationalConfig = z.infer<typeof operationalSchema>;
1037
+ /**
1038
+ * Error thrown when operational configuration is invalid.
1039
+ * @public
1040
+ */
1041
+ declare class OperationalValidationError extends Error {
1042
+ readonly issues: z.ZodIssue[];
1043
+ constructor(message: string, issues: z.ZodIssue[]);
1044
+ }
1045
+ /**
1046
+ * Parse operational configuration content from a string.
1047
+ *
1048
+ * @param content - The operational config file content
1049
+ * @param format - The format of the content ('yaml' or 'json')
1050
+ * @returns Parsed and validated operational configuration
1051
+ * @throws {@link OperationalValidationError} If validation fails
1052
+ * @public
1053
+ */
1054
+ declare function parseOperationalContent(content: string, format: 'yaml' | 'json'): OperationalConfig;
1055
+
1056
+ /**
1057
+ * Merge policy and operational configurations into a unified AttestItConfig.
1058
+ *
1059
+ * This module handles the combination of security-critical policy configuration
1060
+ * (loaded from the default branch) with operational configuration (can be loaded
1061
+ * from PR branches) to create the complete configuration used for attestation
1062
+ * verification.
1063
+ *
1064
+ * @packageDocumentation
1065
+ */
1066
+
1067
+ /**
1068
+ * Merges policy and operational configurations into a single AttestItConfig.
1069
+ *
1070
+ * The merge strategy prioritizes security-critical fields from the policy
1071
+ * configuration while combining operational fields from both sources:
1072
+ *
1073
+ * - **Policy settings** (maxAgeDays, publicKeyPath, attestationsPath) are used as-is
1074
+ * - **Operational settings** (defaultCommand, keyProvider) are added from operational config
1075
+ * - **Team and gates** come exclusively from policy config
1076
+ * - **Suites and groups** come exclusively from operational config
1077
+ *
1078
+ * @param policy - The policy configuration containing security-critical settings
1079
+ * @param operational - The operational configuration containing suites and execution settings
1080
+ * @returns A complete AttestItConfig ready for use in attestation operations
1081
+ *
1082
+ * @example
1083
+ * ```typescript
1084
+ * const policy = parsePolicyContent(policyYaml, 'yaml')
1085
+ * const operational = parseOperationalContent(operationalYaml, 'yaml')
1086
+ * const config = mergeConfigs(policy, operational)
1087
+ * ```
1088
+ *
1089
+ * @public
1090
+ */
1091
+ declare function mergeConfigs(policy: PolicyConfig, operational: OperationalConfig): AttestItConfig;
1092
+
1093
+ /**
1094
+ * Cross-configuration validation for split policy and operational configs.
1095
+ *
1096
+ * This module validates relationships between policy and operational configurations,
1097
+ * ensuring that suite-gate references are valid and that all authorized signers
1098
+ * are defined in the team.
1099
+ *
1100
+ * @packageDocumentation
1101
+ */
1102
+
1103
+ /**
1104
+ * Validation error types for cross-configuration validation.
1105
+ * @public
1106
+ */
1107
+ type ValidationErrorType = 'UNKNOWN_GATE' | 'MISSING_TEAM_MEMBER';
1108
+ /**
1109
+ * Represents a validation error found during cross-configuration validation.
1110
+ * @public
1111
+ */
1112
+ interface ValidationError {
1113
+ /** The type of validation error */
1114
+ type: ValidationErrorType;
1115
+ /** The suite name where the error was found (if applicable) */
1116
+ suite?: string;
1117
+ /** The gate name involved in the error (if applicable) */
1118
+ gate?: string;
1119
+ /** The signer slug that is missing (if applicable) */
1120
+ signer?: string;
1121
+ /** Human-readable error message explaining the issue */
1122
+ message: string;
1123
+ }
1124
+ /**
1125
+ * Validates that all suite-gate references and authorized signers are valid.
1126
+ *
1127
+ * This function performs cross-configuration validation to ensure:
1128
+ * 1. Every suite that references a gate refers to an existing gate in the policy
1129
+ * 2. Every authorized signer in each referenced gate is defined in the policy team
1130
+ *
1131
+ * These validations are critical because:
1132
+ * - Operational config (suites) can come from PR branches
1133
+ * - Policy config (gates, team) comes from the default branch
1134
+ * - We must ensure PR authors cannot reference non-existent gates or signers
1135
+ *
1136
+ * @param policy - The policy configuration containing gates and team definitions
1137
+ * @param operational - The operational configuration containing suite definitions
1138
+ * @returns An array of validation errors (empty if validation passes)
1139
+ *
1140
+ * @example
1141
+ * ```typescript
1142
+ * const errors = validateSuiteGateReferences(policy, operational)
1143
+ * if (errors.length > 0) {
1144
+ * console.error('Validation failed:')
1145
+ * for (const error of errors) {
1146
+ * console.error(` - ${error.message}`)
1147
+ * }
1148
+ * throw new Error('Configuration validation failed')
1149
+ * }
1150
+ * ```
1151
+ *
1152
+ * @public
1153
+ */
1154
+ declare function validateSuiteGateReferences(policy: PolicyConfig, operational: OperationalConfig): ValidationError[];
1155
+
640
1156
  /**
641
1157
  * Options for computing a package fingerprint.
642
1158
  * @public
@@ -1390,6 +1906,18 @@ declare class OnePasswordKeyProvider implements KeyProvider {
1390
1906
  interface MacOSKeychainKeyProviderOptions {
1391
1907
  /** Item name in keychain (e.g., "attest-it-private-key") */
1392
1908
  itemName: string;
1909
+ /** Path to the keychain file (optional, uses default keychain if not specified) */
1910
+ keychain?: string;
1911
+ }
1912
+ /**
1913
+ * Information about a macOS keychain.
1914
+ * @public
1915
+ */
1916
+ interface MacOSKeychain {
1917
+ /** Full path to the keychain file */
1918
+ path: string;
1919
+ /** Display name (filename without extension) */
1920
+ name: string;
1393
1921
  }
1394
1922
  /**
1395
1923
  * Key provider that stores private keys in macOS Keychain.
@@ -1405,6 +1933,7 @@ declare class MacOSKeychainKeyProvider implements KeyProvider {
1405
1933
  readonly type = "macos-keychain";
1406
1934
  readonly displayName = "macOS Keychain";
1407
1935
  private readonly itemName;
1936
+ private readonly keychain?;
1408
1937
  private static readonly ACCOUNT;
1409
1938
  /**
1410
1939
  * Create a new MacOSKeychainKeyProvider.
@@ -1416,6 +1945,11 @@ declare class MacOSKeychainKeyProvider implements KeyProvider {
1416
1945
  * Only available on macOS platforms.
1417
1946
  */
1418
1947
  static isAvailable(): boolean;
1948
+ /**
1949
+ * List available keychains on the system.
1950
+ * @returns Array of keychain information
1951
+ */
1952
+ static listKeychains(): Promise<MacOSKeychain[]>;
1419
1953
  /**
1420
1954
  * Check if this provider is available on the current system.
1421
1955
  */
@@ -1444,6 +1978,165 @@ declare class MacOSKeychainKeyProvider implements KeyProvider {
1444
1978
  getConfig(): KeyProviderConfig;
1445
1979
  }
1446
1980
 
1981
+ /**
1982
+ * YubiKey-based key provider implementation.
1983
+ *
1984
+ * @remarks
1985
+ * This provider stores private keys encrypted with a key derived from YubiKey
1986
+ * HMAC-SHA1 challenge-response. The key cannot be decrypted without the physical
1987
+ * YubiKey present. Uses HKDF to derive an AES-256-GCM encryption key from the
1988
+ * challenge-response output.
1989
+ *
1990
+ * Requires the `ykman` (YubiKey Manager) CLI tool to be installed.
1991
+ *
1992
+ * **Security Note**: Private keys are temporarily held in memory as JavaScript
1993
+ * strings during encryption/decryption. JavaScript strings are immutable and
1994
+ * cannot be securely zeroed. The key remains in memory until garbage collected.
1995
+ * For maximum security, use full-disk encryption and disable swap on systems
1996
+ * handling sensitive keys.
1997
+ *
1998
+ * @packageDocumentation
1999
+ */
2000
+
2001
+ /**
2002
+ * Options for creating a YubiKeyProvider.
2003
+ * @public
2004
+ */
2005
+ interface YubiKeyProviderOptions {
2006
+ /** Path to the encrypted key file */
2007
+ encryptedKeyPath: string;
2008
+ /** YubiKey slot to use for challenge-response (default: 2) */
2009
+ slot?: 1 | 2;
2010
+ /** Serial number of specific YubiKey to use (optional but recommended) */
2011
+ serial?: string;
2012
+ }
2013
+ /**
2014
+ * Information about a connected YubiKey.
2015
+ * @public
2016
+ */
2017
+ interface YubiKeyInfo {
2018
+ /** Device serial number */
2019
+ serial: string;
2020
+ /** Device type (e.g., "YubiKey 5 NFC") */
2021
+ type: string;
2022
+ /** Firmware version */
2023
+ firmware: string;
2024
+ }
2025
+ /**
2026
+ * Key provider that encrypts private keys using YubiKey HMAC challenge-response.
2027
+ *
2028
+ * @remarks
2029
+ * This provider uses the YubiKey HMAC-SHA1 challenge-response feature (typically slot 2)
2030
+ * to derive an encryption key. The Ed25519 private key is encrypted with AES-256-GCM,
2031
+ * and can only be decrypted when the correct YubiKey is present.
2032
+ *
2033
+ * This approach:
2034
+ * - Works with all YubiKeys that support HMAC-SHA1 challenge-response
2035
+ * - Preserves Ed25519 compatibility (signing happens in software)
2036
+ * - Requires physical YubiKey presence to decrypt and use the key
2037
+ *
2038
+ * **Security Note**: Always use the `serial` option to bind keys to a specific YubiKey.
2039
+ * Without serial verification, any YubiKey with the same HMAC secret could decrypt the key.
2040
+ *
2041
+ * @public
2042
+ */
2043
+ declare class YubiKeyProvider implements KeyProvider {
2044
+ readonly type = "yubikey";
2045
+ readonly displayName = "YubiKey";
2046
+ private readonly encryptedKeyPath;
2047
+ private readonly slot;
2048
+ private readonly serial?;
2049
+ /**
2050
+ * Create a new YubiKeyProvider.
2051
+ * @param options - Provider options
2052
+ * @throws Error if encryptedKeyPath is outside the attest-it config directory
2053
+ */
2054
+ constructor(options: YubiKeyProviderOptions);
2055
+ /**
2056
+ * Check if ykman CLI is installed and available.
2057
+ * @returns true if ykman is available
2058
+ */
2059
+ static isInstalled(): Promise<boolean>;
2060
+ /**
2061
+ * Check if any YubiKey is connected.
2062
+ * @returns true if at least one YubiKey is connected
2063
+ */
2064
+ static isConnected(): Promise<boolean>;
2065
+ /**
2066
+ * Check if HMAC challenge-response is configured on a slot.
2067
+ * @param slot - Slot number (1 or 2)
2068
+ * @param serial - Optional YubiKey serial number
2069
+ * @returns true if challenge-response is configured
2070
+ */
2071
+ static isChallengeResponseConfigured(slot?: 1 | 2, serial?: string): Promise<boolean>;
2072
+ /**
2073
+ * List connected YubiKeys.
2074
+ * @returns Array of YubiKey information
2075
+ */
2076
+ static listDevices(): Promise<YubiKeyInfo[]>;
2077
+ /**
2078
+ * Check if this provider is available on the current system.
2079
+ * Requires ykman to be installed.
2080
+ */
2081
+ isAvailable(): Promise<boolean>;
2082
+ /**
2083
+ * Check if an encrypted key file exists.
2084
+ * @param keyRef - Path to encrypted key file
2085
+ */
2086
+ keyExists(keyRef: string): Promise<boolean>;
2087
+ /**
2088
+ * Get the private key by decrypting with YubiKey.
2089
+ * Downloads to a temporary file and returns a cleanup function.
2090
+ *
2091
+ * **Important**: Always call the cleanup function when done to securely delete
2092
+ * the temporary key file. The cleanup is also registered for process exit handlers.
2093
+ *
2094
+ * @param keyRef - Path to encrypted key file
2095
+ * @throws Error if the key cannot be decrypted
2096
+ */
2097
+ getPrivateKey(keyRef: string): Promise<KeyRetrievalResult>;
2098
+ /**
2099
+ * Generate a new keypair and store encrypted with YubiKey.
2100
+ * Public key is written to filesystem for repository commit.
2101
+ *
2102
+ * **Security Note**: Always specify a serial number to bind the key to a specific YubiKey.
2103
+ *
2104
+ * @param options - Key generation options
2105
+ */
2106
+ generateKeyPair(options: KeygenProviderOptions): Promise<KeyGenerationResult>;
2107
+ /**
2108
+ * Encrypt an existing private key with YubiKey challenge-response.
2109
+ *
2110
+ * @remarks
2111
+ * This static method allows encrypting a private key that was generated
2112
+ * elsewhere (e.g., by the CLI) without having to create a provider instance first.
2113
+ *
2114
+ * **Security Note**: Always specify a serial number to bind the key to a specific YubiKey.
2115
+ * The serial provides defense-in-depth by ensuring only the intended YubiKey can decrypt.
2116
+ *
2117
+ * @param options - Encryption options
2118
+ * @returns Path to the encrypted key file and storage description
2119
+ * @public
2120
+ */
2121
+ static encryptPrivateKey(options: {
2122
+ /** The private key content (PEM format) */
2123
+ privateKey: string;
2124
+ /** Path where the encrypted key file will be saved */
2125
+ encryptedKeyPath: string;
2126
+ /** YubiKey slot to use (default: 2) */
2127
+ slot?: 1 | 2;
2128
+ /** YubiKey serial number (recommended for security) */
2129
+ serial?: string;
2130
+ }): Promise<{
2131
+ encryptedKeyPath: string;
2132
+ storageDescription: string;
2133
+ }>;
2134
+ /**
2135
+ * Get the configuration for this provider.
2136
+ */
2137
+ getConfig(): KeyProviderConfig;
2138
+ }
2139
+
1447
2140
  /**
1448
2141
  * Registry for key provider implementations.
1449
2142
  *
@@ -1506,12 +2199,18 @@ type PrivateKeyRef = {
1506
2199
  type: 'keychain';
1507
2200
  service: string;
1508
2201
  account: string;
2202
+ keychain?: string;
1509
2203
  } | {
1510
2204
  type: '1password';
1511
2205
  account?: string;
1512
2206
  vault: string;
1513
2207
  item: string;
1514
2208
  field?: string;
2209
+ } | {
2210
+ type: 'yubikey';
2211
+ encryptedKeyPath: string;
2212
+ slot?: 1 | 2;
2213
+ serial?: string;
1515
2214
  };
1516
2215
  /**
1517
2216
  * A single identity configuration.
@@ -1540,11 +2239,88 @@ interface LocalConfig {
1540
2239
  identities: Record<string, Identity>;
1541
2240
  }
1542
2241
 
2242
+ /**
2243
+ * User preferences management for attest-it.
2244
+ * Stored separately from identity config to allow preferences
2245
+ * before any identity exists.
2246
+ * @packageDocumentation
2247
+ */
2248
+ /**
2249
+ * CLI experience preferences (UX-related settings).
2250
+ * @public
2251
+ */
2252
+ interface CliExperiencePreferences {
2253
+ /** Whether the user has declined shell completion installation */
2254
+ declinedCompletionInstall?: boolean;
2255
+ }
2256
+ /**
2257
+ * User preferences stored in ~/.config/attest-it/preferences.yaml
2258
+ * @public
2259
+ */
2260
+ interface UserPreferences {
2261
+ /** CLI experience and UX settings */
2262
+ cliExperience?: CliExperiencePreferences;
2263
+ }
2264
+ /**
2265
+ * Get the path to the preferences file.
2266
+ *
2267
+ * @returns Path to the preferences file
2268
+ * @public
2269
+ */
2270
+ declare function getPreferencesPath(): string;
2271
+ /**
2272
+ * Load user preferences from file.
2273
+ * Validates the file contents against the schema and returns
2274
+ * only valid, known preferences.
2275
+ *
2276
+ * @returns User preferences, or empty object if file doesn't exist
2277
+ * @public
2278
+ */
2279
+ declare function loadPreferences(): Promise<UserPreferences>;
2280
+ /**
2281
+ * Save user preferences to file.
2282
+ *
2283
+ * @param preferences - Preferences to save
2284
+ * @public
2285
+ */
2286
+ declare function savePreferences(preferences: UserPreferences): Promise<void>;
2287
+ /**
2288
+ * Update a single preference value.
2289
+ *
2290
+ * @param key - Preference key to update
2291
+ * @param value - New value
2292
+ * @public
2293
+ */
2294
+ declare function setPreference<K extends keyof UserPreferences>(key: K, value: UserPreferences[K]): Promise<void>;
2295
+ /**
2296
+ * Get a single preference value.
2297
+ *
2298
+ * @param key - Preference key to get
2299
+ * @returns Preference value, or undefined if not set
2300
+ * @public
2301
+ */
2302
+ declare function getPreference<K extends keyof UserPreferences>(key: K): Promise<UserPreferences[K] | undefined>;
2303
+
1543
2304
  /**
1544
2305
  * Configuration loading for local identity system.
1545
2306
  * @packageDocumentation
1546
2307
  */
1547
2308
 
2309
+ /**
2310
+ * Set a custom home directory for attest-it configuration.
2311
+ * This is useful for testing or running with isolated state.
2312
+ *
2313
+ * @param dir - The directory to use, or null to reset to default
2314
+ * @public
2315
+ */
2316
+ declare function setAttestItHomeDir(dir: string | null): void;
2317
+ /**
2318
+ * Get the current attest-it home directory override.
2319
+ *
2320
+ * @returns The override directory, or null if using default
2321
+ * @public
2322
+ */
2323
+ declare function getAttestItHomeDir(): string | null;
1548
2324
  /**
1549
2325
  * Error thrown when local config validation fails.
1550
2326
  * @public
@@ -1556,10 +2332,23 @@ declare class LocalConfigValidationError extends Error {
1556
2332
  /**
1557
2333
  * Get the path to the local config file.
1558
2334
  *
1559
- * @returns Path to ~/.config/attest-it/config.yaml
2335
+ * If a home directory override is set via setAttestItHomeDir(),
2336
+ * returns {homeDir}/config.yaml. Otherwise returns ~/.config/attest-it/config.yaml.
2337
+ *
2338
+ * @returns Path to the local config file
1560
2339
  * @public
1561
2340
  */
1562
2341
  declare function getLocalConfigPath(): string;
2342
+ /**
2343
+ * Get the attest-it configuration directory.
2344
+ *
2345
+ * If a home directory override is set via setAttestItHomeDir(),
2346
+ * returns that directory. Otherwise returns ~/.config/attest-it.
2347
+ *
2348
+ * @returns Path to the configuration directory
2349
+ * @public
2350
+ */
2351
+ declare function getAttestItConfigDir(): string;
1563
2352
  /**
1564
2353
  * Load and validate local config from file (async).
1565
2354
  *
@@ -1832,4 +2621,4 @@ declare function verifyAllSeals(config: AttestItConfig, seals: SealsFile, finger
1832
2621
  */
1833
2622
  declare const version = "0.0.0";
1834
2623
 
1835
- export { type AttestItConfig, type AttestItSettings, type Attestation, type AttestationsFile, type Config, ConfigNotFoundError, ConfigValidationError, type CreateSealOptions, type VerifyOptions$1 as CryptoVerifyOptions, type KeyPair as Ed25519KeyPair, FilesystemKeyProvider, type FilesystemKeyProviderOptions, type FingerprintConfig, type FingerprintOptions, type FingerprintResult, type GateConfig, type Identity, type KeyGenerationResult, type KeyPaths, type KeyProvider, type KeyProviderConfig, type KeyProviderFactory, KeyProviderRegistry, type KeyProviderSettings, type KeyRetrievalResult, type KeygenOptions, type KeygenProviderOptions, type LocalConfig, LocalConfigValidationError, MacOSKeychainKeyProvider, type MacOSKeychainKeyProviderOptions, type OnePasswordAccount, OnePasswordKeyProvider, type OnePasswordKeyProviderOptions, type OnePasswordVault, type PrivateKeyRef, type ReadSignedAttestationsOptions, type Seal, type SealVerificationResult, type SealsFile, type SignOptions, SignatureInvalidError, type SignatureVerificationResult, type SuiteConfig, type SuiteVerificationResult, type TeamMember, type VerificationState, type VerificationStatus, type VerifyOptions, type VerifyResult, type WriteSignedAttestationsOptions, canonicalizeAttestations, checkOpenSSL, computeFingerprint, computeFingerprintSync, createAttestation, createSeal, findAttestation, findConfigPath, findTeamMemberByPublicKey, generateKeyPair as generateEd25519KeyPair, generateKeyPair$1 as generateKeyPair, getActiveIdentity, getAuthorizedSignersForGate, getDefaultPrivateKeyPath, getDefaultPublicKeyPath, getGate, getLocalConfigPath, getPublicKeyFromPrivate, isAuthorizedSigner, listPackageFiles, loadConfig, loadConfigSync, loadLocalConfig, loadLocalConfigSync, parseDuration, readAndVerifyAttestations, readAttestations, readAttestationsSync, readSeals, readSealsSync, removeAttestation, resolveConfigPaths, saveLocalConfig, saveLocalConfigSync, setKeyPermissions, sign$1 as sign, sign as signEd25519, toAttestItConfig, upsertAttestation, verify$1 as verify, verifyAllSeals, verifyAttestations, verify as verifyEd25519, verifyGateSeal, verifySeal, version, writeAttestations, writeAttestationsSync, writeSeals, writeSealsSync, writeSignedAttestations };
2624
+ export { type AttestItConfig, type AttestItSettings, type Attestation, type AttestationsFile, type CliExperiencePreferences, type Config, ConfigNotFoundError, ConfigValidationError, type CreateSealOptions, type VerifyOptions$1 as CryptoVerifyOptions, type KeyPair as Ed25519KeyPair, FilesystemKeyProvider, type FilesystemKeyProviderOptions, type FingerprintConfig, type FingerprintOptions, type FingerprintResult, type GateConfig, type Identity, type KeyGenerationResult, type KeyPaths, type KeyProvider, type KeyProviderConfig, type KeyProviderFactory, KeyProviderRegistry, type KeyProviderSettings, type KeyRetrievalResult, type KeygenOptions, type KeygenProviderOptions, type LocalConfig, LocalConfigValidationError, type MacOSKeychain, MacOSKeychainKeyProvider, type MacOSKeychainKeyProviderOptions, type OnePasswordAccount, OnePasswordKeyProvider, type OnePasswordKeyProviderOptions, type OnePasswordVault, type OperationalConfig, OperationalValidationError, type PolicyConfig, PolicyValidationError, type PrivateKeyRef, type ReadSignedAttestationsOptions, type Seal, type SealVerificationResult, type SealsFile, type SignOptions, SignatureInvalidError, type SignatureVerificationResult, type SuiteConfig, type SuiteVerificationResult, type TeamMember, type UserPreferences, type ValidationError, type ValidationErrorType, type VerificationState, type VerificationStatus, type VerifyOptions, type VerifyResult, type WriteSignedAttestationsOptions, type YubiKeyInfo, YubiKeyProvider, type YubiKeyProviderOptions, canonicalizeAttestations, checkOpenSSL, computeFingerprint, computeFingerprintSync, createAttestation, createSeal, findAttestation, findConfigPath, findTeamMemberByPublicKey, generateKeyPair as generateEd25519KeyPair, generateKeyPair$1 as generateKeyPair, getActiveIdentity, getAttestItConfigDir, getAttestItHomeDir, getAuthorizedSignersForGate, getDefaultPrivateKeyPath, getDefaultPublicKeyPath, getGate, getLocalConfigPath, getPreference, getPreferencesPath, getPublicKeyFromPrivate, isAuthorizedSigner, listPackageFiles, loadConfig, loadConfigSync, loadLocalConfig, loadLocalConfigSync, loadPreferences, mergeConfigs, operationalSchema, parseDuration, parseOperationalContent, parsePolicyContent, policySchema, readAndVerifyAttestations, readAttestations, readAttestationsSync, readSeals, readSealsSync, removeAttestation, resolveConfigPaths, saveLocalConfig, saveLocalConfigSync, savePreferences, setAttestItHomeDir, setKeyPermissions, setPreference, sign$1 as sign, sign as signEd25519, toAttestItConfig, upsertAttestation, validateSuiteGateReferences, verify$1 as verify, verifyAllSeals, verifyAttestations, verify as verifyEd25519, verifyGateSeal, verifySeal, version, writeAttestations, writeAttestationsSync, writeSeals, writeSealsSync, writeSignedAttestations };