From 0fa2cc855152ddb75a98f92897e0ef0de03e71db Mon Sep 17 00:00:00 2001 From: ajayk Date: Tue, 24 Feb 2026 11:40:42 -0800 Subject: [PATCH] fix: npm audit signatures for keyless attestation registries --- lib/utils/verify-signatures.js | 2 +- .../keyless-sigstore-attestations.json | 54 +++++++++++++++++++ test/lib/commands/audit.js | 41 ++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/sigstore/keyless-sigstore-attestations.json diff --git a/lib/utils/verify-signatures.js b/lib/utils/verify-signatures.js index 2130c847b60ec..c9b39591eadc8 100644 --- a/lib/utils/verify-signatures.js +++ b/lib/utils/verify-signatures.js @@ -45,7 +45,7 @@ class VerifySignatures { // Didn't find any dependencies that could be verified, e.g. only local // deps, missing version, not on a registry etc. - if (!this.auditedWithKeysCount) { + if (!this.auditedWithKeysCount && !this.verifiedAttestationCount) { throw new Error('found no dependencies to audit that were installed from ' + 'a supported registry') } diff --git a/test/fixtures/sigstore/keyless-sigstore-attestations.json b/test/fixtures/sigstore/keyless-sigstore-attestations.json new file mode 100644 index 0000000000000..f2fcb660554b9 --- /dev/null +++ b/test/fixtures/sigstore/keyless-sigstore-attestations.json @@ -0,0 +1,54 @@ +{ + "attestations": [ + { + "predicateType": "https://slsa.dev/provenance/v0.2", + "bundle": { + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + { + "rawBytes": "MIIDmzCCAyGgAwIBAgIUce0wM1Ev1pqBXH9W1BbvEg9RopYwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwMjA5MTc1NjAwWhcNMjMwMjA5MTgwNjAwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0SgHPgKy+HgMtXdqkkgY6Ji1v7wc+lxnnatY73cbKFwQzw+/x8288IwIz6y54dtznSXnjbzNMdNS0Q2rfMsMcaOCAkAwggI8MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUVVMaFO5X4Xzc/tiJCfm0WXa75QIwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wZAYDVR0RAQH/BFowWIZWaHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAVBgorBgEEAYO/MAECBAdyZWxlYXNlMDYGCisGAQQBg78wAQMEKDA2NTI4OTk3YzNjOGFiOWY4NjRmYWQ5YTM2NTg0NDZhY2E3ZmQ4NmQwFQYKKwYBBAGDvzABBAQHcHVibGlzaDAiBgorBgEEAYO/MAEFBBRzaWdzdG9yZS9zaWdzdG9yZS1qczAeBgorBgEEAYO/MAEGBBByZWZzL3RhZ3MvdjEuMC4wMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGGN1HpGQAABAMARjBEAiB/E0+AFpKimPxI/TQDXeCa06+wtpwvLhyPrbHOQYu74gIgB/9fdZD+uHvUBHyptxaGoBxdJfUKEYx9nhaZw2LeZuwwCgYIKoZIzj0EAwMDaAAwZQIxAPW070C7IM0RrAU5rMpP25TFH/rfKvbvqRNnUoPfvIlA9q7Abe8BHIl97pTmf/5vJgIwbZ4myRXGWjB0LUyzplC2GX0kklVGYeqRM5xxsxAK0zwd/U7KjoFIlp/gkyLyiMHH" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "tlogEntries": [ + { + "logIndex": "12988397", + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + }, + "kindVersion": { + "kind": "intoto", + "version": "0.0.2" + }, + "integratedTime": "1675965360", + "inclusionPromise": { + "signedEntryTimestamp": "MEUCID8bfktgHysxMJAIXz6CqqKHGAYPp/X6FZrS9SDtKdbcAiEAg+0zUFNPJKEVX6m33aCU+CRBgWkDNOC8oE4jHoco4kw=" + }, + "inclusionProof": null, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7ImVudmVsb3BlIjp7InBheWxvYWRUeXBlIjoiYXBwbGljYXRpb24vdm5kLmluLXRvdG8ranNvbiIsInNpZ25hdHVyZXMiOlt7InB1YmxpY0tleSI6IkxTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJ0ZWtORFFYbEhaMEYzU1VKQlowbFZZMlV3ZDAweFJYWXhjSEZDV0VnNVZ6RkNZblpGWnpsU2IzQlpkME5uV1VsTGIxcEplbW93UlVGM1RYY0tUbnBGVmsxQ1RVZEJNVlZGUTJoTlRXTXliRzVqTTFKMlkyMVZkVnBIVmpKTlVqUjNTRUZaUkZaUlVVUkZlRlo2WVZka2VtUkhPWGxhVXpGd1ltNVNiQXBqYlRGc1drZHNhR1JIVlhkSWFHTk9UV3BOZDAxcVFUVk5WR014VG1wQmQxZG9ZMDVOYWsxM1RXcEJOVTFVWjNkT2FrRjNWMnBCUVUxR2EzZEZkMWxJQ2t0dldrbDZhakJEUVZGWlNVdHZXa2w2YWpCRVFWRmpSRkZuUVVVd1UyZElVR2RMZVN0SVowMTBXR1J4YTJ0bldUWkthVEYyTjNkaksyeDRibTVoZEZrS056TmpZa3RHZDFGNmR5c3ZlRGd5T0RoSmQwbDZObmsxTkdSMGVtNVRXRzVxWW5wT1RXUk9VekJSTW5KbVRYTk5ZMkZQUTBGclFYZG5aMGs0VFVFMFJ3cEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJWRUpuVGxaSVUxVkZSRVJCUzBKblozSkNaMFZHUWxGalJFRjZRV1JDWjA1V1NGRTBSVVpuVVZWV1ZrMWhDa1pQTlZnMFdIcGpMM1JwU2tObWJUQlhXR0UzTlZGSmQwaDNXVVJXVWpCcVFrSm5kMFp2UVZVek9WQndlakZaYTBWYVlqVnhUbXB3UzBaWGFYaHBORmtLV2tRNGQxcEJXVVJXVWpCU1FWRklMMEpHYjNkWFNWcFhZVWhTTUdOSVRUWk1lVGx1WVZoU2IyUlhTWFZaTWpsMFRETk9jRm96VGpCaU0wcHNURE5PY0FwYU0wNHdZak5LYkV4WGNIcE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wSXhXVzE0Y0dNeVozVmxWekZ6VVVoS2JGcHVUWFprUjBadUNtTjVPVEpOVXpSM1RHcEJkMDlSV1V0TGQxbENRa0ZIUkhaNlFVSkJVVkZ5WVVoU01HTklUVFpNZVRrd1lqSjBiR0pwTldoWk0xSndZakkxZWt4dFpIQUtaRWRvTVZsdVZucGFXRXBxWWpJMU1GcFhOVEJNYlU1MllsUkJWa0puYjNKQ1owVkZRVmxQTDAxQlJVTkNRV1I1V2xkNGJGbFlUbXhOUkZsSFEybHpSd3BCVVZGQ1p6YzRkMEZSVFVWTFJFRXlUbFJKTkU5VWF6TlplazVxVDBkR2FVOVhXVFJPYWxKdFdWZFJOVmxVVFRKT1ZHY3dUa1JhYUZreVJUTmFiVkUwQ2s1dFVYZEdVVmxMUzNkWlFrSkJSMFIyZWtGQ1FrRlJTR05JVm1saVIyeDZZVVJCYVVKbmIzSkNaMFZGUVZsUEwwMUJSVVpDUWxKNllWZGtlbVJIT1hrS1dsTTVlbUZYWkhwa1J6bDVXbE14Y1dONlFXVkNaMjl5UW1kRlJVRlpUeTlOUVVWSFFrSkNlVnBYV25wTU0xSm9Xak5OZG1ScVJYVk5RelIzVFVsSFNncENaMjl5UW1kRlJVRmtXalZCWjFGRFFraHpSV1ZSUWpOQlNGVkJNMVF3ZDJGellraEZWRXBxUjFJMFkyMVhZek5CY1VwTFdISnFaVkJMTXk5b05IQjVDbWRET0hBM2J6UkJRVUZIUjA0eFNIQkhVVUZCUWtGTlFWSnFRa1ZCYVVJdlJUQXJRVVp3UzJsdFVIaEpMMVJSUkZobFEyRXdOaXQzZEhCM2RreG9lVkFLY21KSVQxRlpkVGMwWjBsblFpODVabVJhUkN0MVNIWlZRa2g1Y0hSNFlVZHZRbmhrU21aVlMwVlplRGx1YUdGYWR6Sk1aVnAxZDNkRFoxbEpTMjlhU1FwNmFqQkZRWGROUkdGQlFYZGFVVWw0UVZCWE1EY3dRemRKVFRCU2NrRlZOWEpOY0ZBeU5WUkdTQzl5Wmt0MlluWnhVazV1Vlc5UVpuWkpiRUU1Y1RkQkNtSmxPRUpJU1d3NU4zQlViV1l2TlhaS1owbDNZbG8wYlhsU1dFZFhha0l3VEZWNWVuQnNRekpIV0RCcmEyeFdSMWxsY1ZKTk5YaDRjM2hCU3pCNmQyUUtMMVUzUzJwdlJrbHNjQzluYTNsTWVXbE5TRWdLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89Iiwic2lnIjoiVFVWWlEwbFJSREV3YTBGdU0yeERMekZ5U25aWVFuUlRSR05yWW5GclMwVnRlak0yT1dkUVJFdGlOR3hITkhwTlMxRkphRUZRTVN0U2FHSk5ZMEZUYzJaWWFIaHdXRXRPUTBGcVNtSXJNMEYyTTBKeU9UVmxTMFEzVmt3dlFrVkMifV19LCJoYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiNjI2OWQzNzQ2MzI0MjM5YzEzOGJkZTMzMDEzMTVlY2FkNmI4ZGM5YzcwY2RlYTBhODEyYjYzYTUzOGJmYzdlYyJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6Ijg0NWU5MTRlYmJhZTBkNmZmY2FlMmFmYjc3YzdkZWY0NzkzMDVjOWVlMzExMDE0MDJmZTQ3NWU2ZDIzZjExYzkifX19fQ==" + } + ], + "timestampVerificationData": null + }, + "dsseEnvelope": { + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInN1YmplY3QiOlt7Im5hbWUiOiJwa2c6bnBtL3NpZ3N0b3JlQDEuMC4wIiwiZGlnZXN0Ijp7InNoYTUxMiI6IjdiZWE5ZjZlN2ZmMzdmNWZhYjBiMzZiZjA2MTIwMGZmZjAzMDk5ZmQyZmQ2OTZiOTFkMDRiYzVlNGYyMjVlYjlmZDZlMGNhZGNhZDU0YmE5ODBmNDNmYjM1MmE5OWU4ODEwYjRlMGFiZWI1YzBlZjJjZjkxMDhjZDFmMjU4YjM2In19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MC4yIiwicHJlZGljYXRlIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vY2xpL2doYUB2MCIsImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vbnBtL2NsaUA5LjQuMiJ9LCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3NpZ3N0b3JlL3NpZ3N0b3JlLWpzQHJlZnMvdGFncy92MS4wLjAiLCJkaWdlc3QiOnsic2hhMSI6IjA2NTI4OTk3YzNjOGFiOWY4NjRmYWQ5YTM2NTg0NDZhY2E3ZmQ4NmQifSwiZW50cnlQb2ludCI6InNpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAifSwicGFyYW1ldGVycyI6e30sImVudmlyb25tZW50Ijp7IkdJVEhVQl9BQ1RPUl9JRCI6IjM5ODAyNyIsIkdJVEhVQl9FVkVOVF9OQU1FIjoicmVsZWFzZSIsIkdJVEhVQl9SRUYiOiJyZWZzL3RhZ3MvdjEuMC4wIiwiR0lUSFVCX1JFRl9UWVBFIjoidGFnIiwiR0lUSFVCX1JFUE9TSVRPUlkiOiJzaWdzdG9yZS9zaWdzdG9yZS1qcyIsIkdJVEhVQl9SRVBPU0lUT1JZX0lEIjoiNDk1NTc0NTU1IiwiR0lUSFVCX1JFUE9TSVRPUllfT1dORVJfSUQiOiI3MTA5NjM1MyIsIkdJVEhVQl9SVU5fQVRURU1QVCI6IjEiLCJHSVRIVUJfUlVOX0lEIjoiNDEzNzAyODgxNiIsIkdJVEhVQl9SVU5fTlVNQkVSIjoiOSIsIkdJVEhVQl9TSEEiOiIwNjUyODk5N2MzYzhhYjlmODY0ZmFkOWEzNjU4NDQ2YWNhN2ZkODZkIiwiR0lUSFVCX1dPUktGTE9XX1JFRiI6InNpZ3N0b3JlL3NpZ3N0b3JlLWpzLy5naXRodWIvd29ya2Zsb3dzL3B1Ymxpc2gueW1sQHJlZnMvdGFncy92MS4wLjAiLCJHSVRIVUJfV09SS0ZMT1dfU0hBIjoiMDY1Mjg5OTdjM2M4YWI5Zjg2NGZhZDlhMzY1ODQ0NmFjYTdmZDg2ZCJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSWQiOiI0MTM3MDI4ODE2LTEiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6ZmFsc2UsImVudmlyb25tZW50IjpmYWxzZSwibWF0ZXJpYWxzIjpmYWxzZX0sInJlcHJvZHVjaWJsZSI6ZmFsc2V9LCJtYXRlcmlhbHMiOlt7InVyaSI6ImdpdCtodHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtanMiLCJkaWdlc3QiOnsic2hhMSI6IjA2NTI4OTk3YzNjOGFiOWY4NjRmYWQ5YTM2NTg0NDZhY2E3ZmQ4NmQifX1dfX0=", + "payloadType": "application/vnd.in-toto+json", + "signatures": [ + { + "sig": "MEYCIQD10kAn3lC/1rJvXBtSDckbqkKEmz369gPDKb4lG4zMKQIhAP1+RhbMcASsfXhxpXKNCAjJb+3Av3Br95eKD7VL/BEB", + "keyid": "" + } + ] + } + } + } + ] +} diff --git a/test/lib/commands/audit.js b/test/lib/commands/audit.js index 26853823a72b0..4103ff8dec8c1 100644 --- a/test/lib/commands/audit.js +++ b/test/lib/commands/audit.js @@ -1853,6 +1853,47 @@ t.test('audit signatures', async t => { t.matchSnapshot(joinedOutput()) }) + t.test('with keyless attestations and no registry keys', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: installWithValidAttestations, + mocks: { + pacote: t.mock('pacote', { + sigstore: { verify: async () => true }, + }), + }, + }) + const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') }) + const manifest = registry.manifest({ + name: 'sigstore', + packuments: [{ + version: '1.0.0', + dist: { + integrity: 'sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD' + + '0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==', + tarball: 'https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz', + attestations: { + url: 'https://registry.npmjs.org/-/npm/v1/attestations/sigstore@1.0.0', + provenance: { predicateType: 'https://slsa.dev/provenance/v0.2' }, + }, + }, + }], + }) + await registry.package({ manifest }) + const fixture = fs.readFileSync( + path.resolve(__dirname, '../../fixtures/sigstore/keyless-sigstore-attestations.json'), + 'utf8' + ) + registry.nock.get('/-/npm/v1/attestations/sigstore@1.0.0').reply(200, JSON.parse(fixture)) + // TUF returns no keys and the keys endpoint returns 404 + mockTUF({ npm, target: TUF_TARGET_NOT_FOUND }) + registry.nock.get('/-/npm/v1/keys').reply(404) + + await npm.exec('audit', ['signatures']) + + t.notOk(process.exitCode, 'should exit successfully') + t.match(joinedOutput(), /1 package has a verified attestation/) + }) + t.test('with multiple valid attestations', async t => { const { npm, joinedOutput } = await loadMockNpm(t, { prefixDir: installWithMultipleValidAttestations,