@attest-it/cli 0.7.0 → 0.8.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/bin/attest-it.js +113 -21
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +112 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +113 -21
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/bin/attest-it.js
CHANGED
|
@@ -16,7 +16,7 @@ import { useState, useEffect } from 'react';
|
|
|
16
16
|
import { render, useApp, Box, Text, useInput } from 'ink';
|
|
17
17
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
18
18
|
import { mkdir, writeFile, unlink, readFile } from 'fs/promises';
|
|
19
|
-
import { Spinner, Select, TextInput } from '@inkjs/ui';
|
|
19
|
+
import { Spinner, Select, PasswordInput, TextInput } from '@inkjs/ui';
|
|
20
20
|
import { stringify } from 'yaml';
|
|
21
21
|
import { fileURLToPath } from 'url';
|
|
22
22
|
|
|
@@ -337,7 +337,7 @@ async function runStatus(gates, options) {
|
|
|
337
337
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
338
338
|
}
|
|
339
339
|
const projectRoot = process.cwd();
|
|
340
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
340
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
341
341
|
const gatesToCheck = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
|
|
342
342
|
for (const gateId of gatesToCheck) {
|
|
343
343
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -989,12 +989,24 @@ async function getAllSuiteStatuses(config) {
|
|
|
989
989
|
const attestations = attestationsFile?.attestations ?? [];
|
|
990
990
|
const results = [];
|
|
991
991
|
for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
|
|
992
|
-
|
|
992
|
+
let packages;
|
|
993
|
+
let ignore;
|
|
994
|
+
if (suiteConfig.gate && config.gates) {
|
|
995
|
+
const gateConfig = config.gates[suiteConfig.gate];
|
|
996
|
+
if (gateConfig) {
|
|
997
|
+
packages = gateConfig.fingerprint.paths;
|
|
998
|
+
ignore = gateConfig.fingerprint.exclude;
|
|
999
|
+
}
|
|
1000
|
+
} else if (suiteConfig.packages) {
|
|
1001
|
+
packages = suiteConfig.packages;
|
|
1002
|
+
ignore = suiteConfig.ignore;
|
|
1003
|
+
}
|
|
1004
|
+
if (!packages || packages.length === 0) {
|
|
993
1005
|
continue;
|
|
994
1006
|
}
|
|
995
1007
|
const fingerprintResult = await computeFingerprint({
|
|
996
|
-
packages
|
|
997
|
-
...
|
|
1008
|
+
packages,
|
|
1009
|
+
...ignore && { ignore }
|
|
998
1010
|
});
|
|
999
1011
|
const attestation = findAttestation(
|
|
1000
1012
|
{ schemaVersion: "1", attestations, signature: "" },
|
|
@@ -1559,18 +1571,19 @@ async function promptForSeal(suiteName, gateId, config) {
|
|
|
1559
1571
|
const fs4 = await import('fs/promises');
|
|
1560
1572
|
const privateKeyPem = await fs4.readFile(keyResult.keyPath, "utf8");
|
|
1561
1573
|
await keyResult.cleanup();
|
|
1574
|
+
const identitySlug = localConfig.activeIdentity;
|
|
1562
1575
|
const seal = createSeal({
|
|
1563
1576
|
gateId,
|
|
1564
1577
|
fingerprint: gateFingerprint.fingerprint,
|
|
1565
|
-
sealedBy:
|
|
1578
|
+
sealedBy: identitySlug,
|
|
1566
1579
|
privateKey: privateKeyPem
|
|
1567
1580
|
});
|
|
1568
1581
|
const projectRoot = process.cwd();
|
|
1569
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
1582
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
1570
1583
|
sealsFile.seals[gateId] = seal;
|
|
1571
|
-
writeSealsSync(projectRoot, sealsFile);
|
|
1584
|
+
writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
|
|
1572
1585
|
success(`Seal created for gate '${gateId}'`);
|
|
1573
|
-
log(` Sealed by: ${identity.name}`);
|
|
1586
|
+
log(` Sealed by: ${identitySlug} (${identity.name})`);
|
|
1574
1587
|
log(` Timestamp: ${seal.timestamp}`);
|
|
1575
1588
|
} catch (err) {
|
|
1576
1589
|
if (err instanceof Error) {
|
|
@@ -1638,6 +1651,7 @@ function getKeyRefFromIdentity(identity) {
|
|
|
1638
1651
|
}
|
|
1639
1652
|
}
|
|
1640
1653
|
}
|
|
1654
|
+
var MIN_PASSPHRASE_LENGTH = 8;
|
|
1641
1655
|
function KeygenInteractive(props) {
|
|
1642
1656
|
const { onComplete, onCancel, onError } = props;
|
|
1643
1657
|
const [step, setStep] = useState("checking-providers");
|
|
@@ -1656,6 +1670,7 @@ function KeygenInteractive(props) {
|
|
|
1656
1670
|
const [selectedYubiKeySlot, setSelectedYubiKeySlot] = useState(2);
|
|
1657
1671
|
const [slot1Configured, setSlot1Configured] = useState(false);
|
|
1658
1672
|
const [slot2Configured, setSlot2Configured] = useState(false);
|
|
1673
|
+
const [encryptionPassphrase, setEncryptionPassphrase] = useState();
|
|
1659
1674
|
useInput((_input, key) => {
|
|
1660
1675
|
if (key.escape) {
|
|
1661
1676
|
onCancel();
|
|
@@ -1729,7 +1744,7 @@ function KeygenInteractive(props) {
|
|
|
1729
1744
|
const handleProviderSelect = (value) => {
|
|
1730
1745
|
if (value === "filesystem") {
|
|
1731
1746
|
setSelectedProvider("filesystem");
|
|
1732
|
-
|
|
1747
|
+
setStep("select-filesystem-encryption");
|
|
1733
1748
|
} else if (value === "1password") {
|
|
1734
1749
|
setSelectedProvider("1password");
|
|
1735
1750
|
if (accounts.length === 1 && accounts[0]) {
|
|
@@ -1783,6 +1798,31 @@ function KeygenInteractive(props) {
|
|
|
1783
1798
|
onError(new Error("YubiKey setup cancelled"));
|
|
1784
1799
|
}
|
|
1785
1800
|
};
|
|
1801
|
+
const handleEncryptionMethodSelect = (value) => {
|
|
1802
|
+
if (value === "passphrase") {
|
|
1803
|
+
setStep("enter-encryption-passphrase");
|
|
1804
|
+
} else {
|
|
1805
|
+
setEncryptionPassphrase(void 0);
|
|
1806
|
+
void generateKeys("filesystem");
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
const handleEncryptionPassphrase = (value) => {
|
|
1810
|
+
if (value.length < MIN_PASSPHRASE_LENGTH) {
|
|
1811
|
+
onError(new Error(`Passphrase must be at least ${String(MIN_PASSPHRASE_LENGTH)} characters`));
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
setEncryptionPassphrase(value);
|
|
1815
|
+
setStep("confirm-encryption-passphrase");
|
|
1816
|
+
};
|
|
1817
|
+
const handleConfirmPassphrase = (value) => {
|
|
1818
|
+
if (value !== encryptionPassphrase) {
|
|
1819
|
+
onError(new Error("Passphrases do not match. Please try again."));
|
|
1820
|
+
setEncryptionPassphrase(void 0);
|
|
1821
|
+
setStep("enter-encryption-passphrase");
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
void generateKeys("filesystem");
|
|
1825
|
+
};
|
|
1786
1826
|
const setupYubiKeySlot = async () => {
|
|
1787
1827
|
setStep("yubikey-configuring");
|
|
1788
1828
|
try {
|
|
@@ -1814,17 +1854,26 @@ function KeygenInteractive(props) {
|
|
|
1814
1854
|
const publicKeyPath = props.publicKeyPath ?? getDefaultPublicKeyPath();
|
|
1815
1855
|
if (provider === "filesystem") {
|
|
1816
1856
|
const fsProvider = new FilesystemKeyProvider();
|
|
1817
|
-
const genOptions = {
|
|
1857
|
+
const genOptions = {
|
|
1858
|
+
publicKeyPath
|
|
1859
|
+
};
|
|
1818
1860
|
if (props.force !== void 0) {
|
|
1819
1861
|
genOptions.force = props.force;
|
|
1820
1862
|
}
|
|
1863
|
+
if (encryptionPassphrase !== void 0) {
|
|
1864
|
+
genOptions.passphrase = encryptionPassphrase;
|
|
1865
|
+
}
|
|
1821
1866
|
const result = await fsProvider.generateKeyPair(genOptions);
|
|
1822
|
-
|
|
1867
|
+
const completionResult = {
|
|
1823
1868
|
provider: "filesystem",
|
|
1824
1869
|
publicKeyPath: result.publicKeyPath,
|
|
1825
1870
|
privateKeyRef: result.privateKeyRef,
|
|
1826
1871
|
storageDescription: result.storageDescription
|
|
1827
|
-
}
|
|
1872
|
+
};
|
|
1873
|
+
if (result.encrypted) {
|
|
1874
|
+
completionResult.encrypted = result.encrypted;
|
|
1875
|
+
}
|
|
1876
|
+
onComplete(completionResult);
|
|
1828
1877
|
} else if (provider === "1password") {
|
|
1829
1878
|
if (!selectedVault || !itemName) {
|
|
1830
1879
|
throw new Error("Vault and item name are required for 1Password");
|
|
@@ -1908,6 +1957,8 @@ function KeygenInteractive(props) {
|
|
|
1908
1957
|
setStep("done");
|
|
1909
1958
|
} catch (err) {
|
|
1910
1959
|
onError(err instanceof Error ? err : new Error("Key generation failed"));
|
|
1960
|
+
} finally {
|
|
1961
|
+
setEncryptionPassphrase(void 0);
|
|
1911
1962
|
}
|
|
1912
1963
|
};
|
|
1913
1964
|
if (step === "checking-providers") {
|
|
@@ -1947,6 +1998,39 @@ function KeygenInteractive(props) {
|
|
|
1947
1998
|
/* @__PURE__ */ jsx(Select, { options, onChange: handleProviderSelect })
|
|
1948
1999
|
] });
|
|
1949
2000
|
}
|
|
2001
|
+
if (step === "select-filesystem-encryption") {
|
|
2002
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2003
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Would you like to encrypt your private key with a passphrase?" }),
|
|
2004
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "A passphrase adds extra security but must be entered each time you sign." }),
|
|
2005
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2006
|
+
/* @__PURE__ */ jsx(
|
|
2007
|
+
Select,
|
|
2008
|
+
{
|
|
2009
|
+
options: [
|
|
2010
|
+
{ label: "No encryption (key protected by file permissions only)", value: "none" },
|
|
2011
|
+
{ label: "Passphrase protection (AES-256 encryption)", value: "passphrase" }
|
|
2012
|
+
],
|
|
2013
|
+
onChange: handleEncryptionMethodSelect
|
|
2014
|
+
}
|
|
2015
|
+
)
|
|
2016
|
+
] });
|
|
2017
|
+
}
|
|
2018
|
+
if (step === "enter-encryption-passphrase") {
|
|
2019
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2020
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Enter a passphrase to encrypt your private key:" }),
|
|
2021
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `(Minimum ${String(MIN_PASSPHRASE_LENGTH)} characters. You will need this passphrase each time you sign.)` }),
|
|
2022
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2023
|
+
/* @__PURE__ */ jsx(PasswordInput, { onSubmit: handleEncryptionPassphrase })
|
|
2024
|
+
] });
|
|
2025
|
+
}
|
|
2026
|
+
if (step === "confirm-encryption-passphrase") {
|
|
2027
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2028
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Confirm your passphrase:" }),
|
|
2029
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "(Enter the same passphrase again to confirm.)" }),
|
|
2030
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2031
|
+
/* @__PURE__ */ jsx(PasswordInput, { onSubmit: handleConfirmPassphrase })
|
|
2032
|
+
] });
|
|
2033
|
+
}
|
|
1950
2034
|
if (step === "select-account") {
|
|
1951
2035
|
const options = accounts.map((account) => ({
|
|
1952
2036
|
label: account.name ? `${account.name} (${account.email})` : account.email,
|
|
@@ -2362,7 +2446,7 @@ async function runVerify(gates, options) {
|
|
|
2362
2446
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2363
2447
|
}
|
|
2364
2448
|
const projectRoot = process.cwd();
|
|
2365
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
2449
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
2366
2450
|
const gatesToVerify = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
|
|
2367
2451
|
for (const gateId of gatesToVerify) {
|
|
2368
2452
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -2518,7 +2602,7 @@ async function runSeal(gates, options) {
|
|
|
2518
2602
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2519
2603
|
}
|
|
2520
2604
|
const projectRoot = process.cwd();
|
|
2521
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
2605
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
2522
2606
|
const gatesToSeal = gates.length > 0 ? gates : getAllGateIds(attestItConfig);
|
|
2523
2607
|
for (const gateId of gatesToSeal) {
|
|
2524
2608
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -2531,9 +2615,17 @@ async function runSeal(gates, options) {
|
|
|
2531
2615
|
skipped: [],
|
|
2532
2616
|
failed: []
|
|
2533
2617
|
};
|
|
2618
|
+
const identitySlug = localConfig.activeIdentity;
|
|
2534
2619
|
for (const gateId of gatesToSeal) {
|
|
2535
2620
|
try {
|
|
2536
|
-
const result = await processSingleGate(
|
|
2621
|
+
const result = await processSingleGate(
|
|
2622
|
+
gateId,
|
|
2623
|
+
attestItConfig,
|
|
2624
|
+
identity,
|
|
2625
|
+
identitySlug,
|
|
2626
|
+
sealsFile,
|
|
2627
|
+
options
|
|
2628
|
+
);
|
|
2537
2629
|
if (result.sealed) {
|
|
2538
2630
|
summary.sealed.push(gateId);
|
|
2539
2631
|
} else if (result.skipped) {
|
|
@@ -2545,7 +2637,7 @@ async function runSeal(gates, options) {
|
|
|
2545
2637
|
}
|
|
2546
2638
|
}
|
|
2547
2639
|
if (!options.dryRun && summary.sealed.length > 0) {
|
|
2548
|
-
writeSealsSync(projectRoot, sealsFile);
|
|
2640
|
+
writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
|
|
2549
2641
|
}
|
|
2550
2642
|
displaySummary(summary, options.dryRun);
|
|
2551
2643
|
if (summary.failed.length > 0) {
|
|
@@ -2564,7 +2656,7 @@ async function runSeal(gates, options) {
|
|
|
2564
2656
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2565
2657
|
}
|
|
2566
2658
|
}
|
|
2567
|
-
async function processSingleGate(gateId, config, identity, sealsFile, options) {
|
|
2659
|
+
async function processSingleGate(gateId, config, identity, identitySlug, sealsFile, options) {
|
|
2568
2660
|
verbose(`Processing gate: ${gateId}`);
|
|
2569
2661
|
const gate = getGate(config, gateId);
|
|
2570
2662
|
if (!gate) {
|
|
@@ -2604,12 +2696,12 @@ async function processSingleGate(gateId, config, identity, sealsFile, options) {
|
|
|
2604
2696
|
const seal = createSeal({
|
|
2605
2697
|
gateId,
|
|
2606
2698
|
fingerprint: fingerprintResult.fingerprint,
|
|
2607
|
-
sealedBy:
|
|
2699
|
+
sealedBy: identitySlug,
|
|
2608
2700
|
privateKey: privateKeyPem
|
|
2609
2701
|
});
|
|
2610
2702
|
sealsFile.seals[gateId] = seal;
|
|
2611
2703
|
log(` Sealed gate: ${gateId}`);
|
|
2612
|
-
verbose(` Sealed by: ${identity.name}`);
|
|
2704
|
+
verbose(` Sealed by: ${identitySlug} (${identity.name})`);
|
|
2613
2705
|
verbose(` Timestamp: ${seal.timestamp}`);
|
|
2614
2706
|
return { sealed: true, skipped: false };
|
|
2615
2707
|
}
|
|
@@ -4046,7 +4138,7 @@ async function runRemove2(slug, options) {
|
|
|
4046
4138
|
const projectRoot = process.cwd();
|
|
4047
4139
|
let sealsFile;
|
|
4048
4140
|
try {
|
|
4049
|
-
sealsFile = readSealsSync(projectRoot);
|
|
4141
|
+
sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
4050
4142
|
} catch {
|
|
4051
4143
|
sealsFile = { version: 1, seals: {} };
|
|
4052
4144
|
}
|