iterate-skill-installer 3.9.4 → 3.11.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/lib/installer.js +43 -5
- package/package.json +1 -1
package/lib/installer.js
CHANGED
|
@@ -166,6 +166,25 @@ const ITERATE_SIGNING_PUBLIC_KEY = [
|
|
|
166
166
|
'-----END PGP PUBLIC KEY BLOCK-----',
|
|
167
167
|
].join('\n');
|
|
168
168
|
|
|
169
|
+
//: SHA-1 fingerprint of the pinned signing key (uid jingzhao-l (sign-github)).
|
|
170
|
+
//: Verification is only accepted when the ``VALIDSIG`` fingerprint belongs to
|
|
171
|
+
//: this key, not merely on a GOODSIG/exit-0 — a substituted signer whose key
|
|
172
|
+
//: gpg auto-retrieved would otherwise pass. Keep in sync with
|
|
173
|
+
//: ``iterate_cli/signing.py`` and ``scripts/install.py``.
|
|
174
|
+
const ITERATE_SIGNING_KEY_FINGERPRINT = '0929EA31DF4F7429F63FC53189D88B1D043A1298';
|
|
175
|
+
|
|
176
|
+
//: Extract the fingerprints gpg reported as ``VALIDSIG`` in ``--status-fd``
|
|
177
|
+
//: output. A status line is ``[GNUPG:] VALIDSIG <fpr> <date> …``.
|
|
178
|
+
function validsigFingerprints(statusOutput) {
|
|
179
|
+
const fprs = [];
|
|
180
|
+
for (const line of String(statusOutput || '').split('\n')) {
|
|
181
|
+
if (!line.startsWith('[GNUPG:] VALIDSIG ')) continue;
|
|
182
|
+
const parts = line.split(/\s+/);
|
|
183
|
+
if (parts.length >= 3 && parts[2]) fprs.push(parts[2].toUpperCase());
|
|
184
|
+
}
|
|
185
|
+
return fprs;
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
const ITERATE_BANNER = [
|
|
170
189
|
'██╗████████╗███████╗██████╗ █████╗ ████████╗███████╗',
|
|
171
190
|
'██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝',
|
|
@@ -593,7 +612,13 @@ function verifyGpgDetachedSignature(artifactPath, signaturePath, publicKeyArmor,
|
|
|
593
612
|
return { status: 'unavailable', detail: 'gpg binary not found' };
|
|
594
613
|
}
|
|
595
614
|
if (importResult.error.code === 'ETIMEDOUT' || importResult.signal === 'SIGKILL') {
|
|
596
|
-
|
|
615
|
+
// A hung gpg is not missing evidence: classify it 'invalid' so the
|
|
616
|
+
// ITERATE_ALLOW_UNSIGNED override cannot bypass authenticity (fail closed,
|
|
617
|
+
// mirroring the verify-timeout branch below and scripts/install.py).
|
|
618
|
+
return {
|
|
619
|
+
status: 'invalid',
|
|
620
|
+
detail: `gpg import timed out after ${timeout} ms (treated as invalid)`,
|
|
621
|
+
};
|
|
597
622
|
}
|
|
598
623
|
return { status: 'unavailable', detail: importResult.error.message };
|
|
599
624
|
}
|
|
@@ -610,17 +635,30 @@ function verifyGpgDetachedSignature(artifactPath, signaturePath, publicKeyArmor,
|
|
|
610
635
|
|
|
611
636
|
const verifyResult = spawnSync(
|
|
612
637
|
'gpg',
|
|
613
|
-
['--homedir', gpgHome, '--batch', '--quiet', '--no-autostart', '--verify', signaturePath, artifactPath],
|
|
638
|
+
['--homedir', gpgHome, '--batch', '--quiet', '--no-autostart', '--no-auto-key-retrieve', '--status-fd', '1', '--verify', signaturePath, artifactPath],
|
|
614
639
|
{ encoding: 'utf8', timeout, killSignal: 'SIGKILL' }
|
|
615
640
|
);
|
|
616
641
|
if (verifyResult.error) {
|
|
617
642
|
if (verifyResult.error.code === 'ETIMEDOUT' || verifyResult.signal === 'SIGKILL') {
|
|
618
|
-
|
|
643
|
+
// A hung gpg is not a missing dependency: classify it as 'invalid' so the
|
|
644
|
+
// ITERATE_ALLOW_UNSIGNED override cannot bypass authenticity. Mirrors
|
|
645
|
+
// iterate_cli/signing.py and scripts/install.py (keep in sync).
|
|
646
|
+
return {
|
|
647
|
+
status: 'invalid',
|
|
648
|
+
detail: `gpg verify timed out after ${timeout} ms (treated as invalid)`,
|
|
649
|
+
};
|
|
619
650
|
}
|
|
620
651
|
return { status: 'unavailable', detail: `gpg launch failed: ${verifyResult.error.message}` };
|
|
621
652
|
}
|
|
622
|
-
//
|
|
623
|
-
|
|
653
|
+
// A GOODSIG (or exit 0) alone does not prove the signer is the pinned key: a
|
|
654
|
+
// substituted signer whose key gpg auto-retrieved would pass. Require a
|
|
655
|
+
// VALIDSIG fingerprint belonging to the pinned key, mirroring
|
|
656
|
+
// iterate_cli/signing.py and scripts/install.py (keep in sync).
|
|
657
|
+
const signerFingerprints = validsigFingerprints(verifyResult.stdout);
|
|
658
|
+
const matchesPinned = signerFingerprints.some(
|
|
659
|
+
(fpr) => fpr === ITERATE_SIGNING_KEY_FINGERPRINT
|
|
660
|
+
);
|
|
661
|
+
if (verifyResult.status === 0 && matchesPinned) {
|
|
624
662
|
return { status: 'verified', detail: (verifyResult.stderr || '').trim() };
|
|
625
663
|
}
|
|
626
664
|
return { status: 'invalid', detail: (verifyResult.stderr || '').trim() || `gpg exited ${verifyResult.status}` };
|