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