@sirmrmarty/n8n-nodes-tmux-orchestrator
Version:
n8n nodes for orchestrating Claude AI agents through tmux sessions
558 lines • 22.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CryptographicQASystem = exports.SecureKeyManager = void 0;
const crypto_1 = require("crypto");
const ed25519 = __importStar(require("@noble/ed25519"));
const sha512_1 = require("@noble/hashes/sha512");
const threadSafeState_1 = require("./threadSafeState");
ed25519.etc.sha512Sync = (...m) => (0, sha512_1.sha512)(ed25519.etc.concatBytes(...m));
class SecureMemory {
static zeroize(buffer) {
if (buffer && buffer.length > 0) {
for (let i = 0; i < buffer.length; i++) {
buffer[i] = 0;
}
const random = (0, crypto_1.randomBytes)(buffer.length);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = random[i];
}
for (let i = 0; i < buffer.length; i++) {
buffer[i] = 0;
}
}
}
static secureCopy(source) {
const copy = new Uint8Array(source.length);
copy.set(source);
return copy;
}
}
class SecurePrivateKey {
constructor(privateKey) {
this.keyData = null;
this.isDestroyed = false;
this.createdAt = Date.now();
if (!privateKey || privateKey.length !== 32) {
throw new Error('Invalid private key: must be 32 bytes');
}
this.keyData = SecureMemory.secureCopy(privateKey);
this.keyId = (0, crypto_1.randomBytes)(8).toString('hex');
setTimeout(() => {
if (!this.isDestroyed) {
this.destroy();
}
}, 5 * 60 * 1000);
}
use(callback) {
if (this.isDestroyed || !this.keyData) {
throw new Error('SecurePrivateKey has been destroyed');
}
try {
const tempKey = SecureMemory.secureCopy(this.keyData);
try {
return callback(tempKey);
}
finally {
SecureMemory.zeroize(tempKey);
}
}
catch (error) {
throw new Error(`Cryptographic operation failed: ${error.message}`);
}
}
async useAsync(callback) {
if (this.isDestroyed || !this.keyData) {
throw new Error('SecurePrivateKey has been destroyed');
}
try {
const tempKey = SecureMemory.secureCopy(this.keyData);
try {
return await callback(tempKey);
}
finally {
SecureMemory.zeroize(tempKey);
}
}
catch (error) {
throw new Error(`Cryptographic operation failed: ${error.message}`);
}
}
async getPublicKey() {
return this.useAsync(async (privateKey) => {
return await ed25519.getPublicKey(privateKey);
});
}
async getPublicKeyHex() {
const publicKey = await this.getPublicKey();
return Buffer.from(publicKey).toString('hex');
}
async sign(data) {
return this.useAsync(async (privateKey) => {
return await ed25519.sign(data, privateKey);
});
}
isValid() {
return !this.isDestroyed && this.keyData !== null;
}
getMetadata() {
return {
keyId: this.keyId,
createdAt: this.createdAt,
isValid: this.isValid()
};
}
destroy() {
if (this.keyData) {
SecureMemory.zeroize(this.keyData);
this.keyData = null;
}
this.isDestroyed = true;
}
}
class SecureKeyManager {
static async createSecureKey(source) {
let keyBytes;
if (typeof source === 'string') {
if (!/^[a-fA-F0-9]{64}$/.test(source)) {
throw new Error('Invalid hex private key format');
}
keyBytes = new Uint8Array(Buffer.from(source, 'hex'));
}
else if (source instanceof Buffer) {
keyBytes = new Uint8Array(source);
}
else {
keyBytes = source;
}
const secureKey = new SecurePrivateKey(keyBytes);
try {
await this.activeKeys.safeSet(secureKey.getMetadata().keyId, secureKey);
}
catch (error) {
console.warn('Failed to track secure key:', error.message);
}
setTimeout(async () => {
await this.cleanupDestroyedKeys();
}, 60000);
return secureKey;
}
static async generateSecureKeyPair() {
const privateKeyBytes = (0, crypto_1.randomBytes)(32);
const privateKey = await this.createSecureKey(privateKeyBytes);
SecureMemory.zeroize(privateKeyBytes);
const publicKeyHex = await privateKey.getPublicKeyHex();
return {
privateKey,
publicKeyHex
};
}
static async cleanupDestroyedKeys() {
try {
const entries = this.activeKeys.safeEntries();
for (const [keyId, key] of entries) {
if (!key.isValid()) {
try {
await this.activeKeys.safeDelete(keyId);
}
catch (error) {
console.warn(`Failed to cleanup key ${keyId}:`, error.message);
}
}
}
}
catch (error) {
console.error('Failed to cleanup destroyed keys:', error.message);
}
}
static async getKeyStats() {
await this.cleanupDestroyedKeys();
let oldestAge = 0;
const now = Date.now();
for (const key of this.activeKeys.safeValues()) {
if (key.isValid()) {
const age = now - key.getMetadata().createdAt;
oldestAge = Math.max(oldestAge, age);
}
}
return {
activeKeys: this.activeKeys.safeSize(),
oldestKeyAge: oldestAge
};
}
}
exports.SecureKeyManager = SecureKeyManager;
SecureKeyManager.activeKeys = threadSafeState_1.stateManager.getMap('activeKeys');
SecureKeyManager.keyRotationInterval = 24 * 60 * 60 * 1000;
class CryptographicQASystem {
static async generateQAKeyPair() {
try {
const keyPair = await SecureKeyManager.generateSecureKeyPair();
const publicKey = await keyPair.privateKey.getPublicKey();
return {
privateKey: keyPair.privateKey,
publicKey,
publicKeyHex: keyPair.publicKeyHex
};
}
catch (error) {
throw new Error(`Failed to generate secure key pair: ${error.message}`);
}
}
static async registerQAEngineer(qaEngineerID, publicKey, masterAuthSignature) {
try {
if (!qaEngineerID || typeof qaEngineerID !== 'string') {
throw new Error('Invalid QA Engineer ID');
}
if (!/^[a-fA-F0-9]{64}$/.test(publicKey)) {
throw new Error('Invalid public key format (must be 64-char hex)');
}
const publicKeyBytes = Buffer.from(publicKey, 'hex');
if (publicKeyBytes.length !== 32) {
throw new Error('Invalid public key length');
}
try {
await this.verifiedPublicKeys.safeSet(qaEngineerID, {
publicKey,
verifiedAt: Date.now(),
qaEngineerID
});
}
catch (error) {
console.error('Failed to store verified public key:', error.message);
throw new Error('Failed to register QA Engineer');
}
return true;
}
catch (error) {
throw new Error(`Failed to register QA Engineer: ${error.message}`);
}
}
static async createQAApproval(approvalData, securePrivateKey) {
try {
this.validateApprovalData(approvalData);
if (!securePrivateKey.isValid()) {
throw new Error('Invalid or destroyed private key');
}
approvalData.approvalNonce = (0, crypto_1.randomBytes)(16).toString('hex');
approvalData.approvalTimestamp = Date.now();
approvalData.expirationTimestamp = Date.now() + this.APPROVAL_VALIDITY_DURATION;
const dataToSign = this.serializeApprovalData(approvalData);
const dataHash = (0, crypto_1.createHash)('sha256').update(dataToSign).digest();
const signature = await securePrivateKey.sign(dataHash);
const publicKeyHex = await securePrivateKey.getPublicKeyHex();
const approval = {
data: approvalData,
signature: Buffer.from(signature).toString('hex'),
publicKey: publicKeyHex
};
return approval;
}
catch (error) {
const safeError = error.message.replace(/[a-fA-F0-9]{64}/g, '[REDACTED_KEY]');
throw new Error(`Failed to create QA approval: ${safeError}`);
}
}
static async verifyQAApproval(approval) {
try {
if (!approval || !approval.data || !approval.signature || !approval.publicKey) {
return { valid: false, reason: 'Invalid approval structure' };
}
if (Date.now() > approval.data.expirationTimestamp) {
return { valid: false, reason: 'Approval has expired' };
}
const registeredKey = this.verifiedPublicKeys.safeGet(approval.data.qaEngineerID);
if (!registeredKey) {
return { valid: false, reason: 'QA Engineer not registered' };
}
if (!(0, crypto_1.timingSafeEqual)(Buffer.from(registeredKey.publicKey, 'hex'), Buffer.from(approval.publicKey, 'hex'))) {
return { valid: false, reason: 'Public key does not match registered key' };
}
const qualityCheck = this.verifyQualityRequirements(approval.data.testResults);
if (!qualityCheck.valid) {
return { valid: false, reason: `Quality requirements not met: ${qualityCheck.reason}` };
}
const dataToSign = this.serializeApprovalData(approval.data);
const dataHash = (0, crypto_1.createHash)('sha256').update(dataToSign).digest();
const signature = Buffer.from(approval.signature, 'hex');
const publicKey = Buffer.from(approval.publicKey, 'hex');
const signatureValid = await ed25519.verify(signature, dataHash, publicKey);
if (!signatureValid) {
return { valid: false, reason: 'Invalid cryptographic signature' };
}
return { valid: true };
}
catch (error) {
return { valid: false, reason: `Verification error: ${error.message}` };
}
}
static validateApprovalData(data) {
if (!data.projectName || typeof data.projectName !== 'string') {
throw new Error('Invalid project name');
}
if (!data.commitHash || !/^[a-fA-F0-9]{40}$/.test(data.commitHash)) {
throw new Error('Invalid commit hash');
}
if (!data.commitMessage || typeof data.commitMessage !== 'string') {
throw new Error('Invalid commit message');
}
if (!data.qaEngineerID || typeof data.qaEngineerID !== 'string') {
throw new Error('Invalid QA Engineer ID');
}
if (!data.testResults || typeof data.testResults !== 'object') {
throw new Error('Invalid test results');
}
const { testResults } = data;
if (typeof testResults.unit !== 'boolean' ||
typeof testResults.integration !== 'boolean' ||
typeof testResults.security !== 'boolean' ||
typeof testResults.performance !== 'boolean') {
throw new Error('Invalid test result format');
}
if (!Number.isInteger(testResults.coverage) || testResults.coverage < 0 || testResults.coverage > 100) {
throw new Error('Invalid coverage percentage');
}
if (!Number.isInteger(testResults.passedTests) || testResults.passedTests < 0) {
throw new Error('Invalid passed tests count');
}
if (!Number.isInteger(testResults.totalTests) || testResults.totalTests < 0) {
throw new Error('Invalid total tests count');
}
if (!Array.isArray(testResults.criticalIssues)) {
throw new Error('Critical issues must be an array');
}
}
static verifyQualityRequirements(testResults) {
if (!testResults.unit) {
return { valid: false, reason: 'Unit tests failed' };
}
if (!testResults.integration) {
return { valid: false, reason: 'Integration tests failed' };
}
if (!testResults.security) {
return { valid: false, reason: 'Security tests failed' };
}
if (!testResults.performance) {
return { valid: false, reason: 'Performance tests failed' };
}
if (testResults.coverage < this.MIN_COVERAGE_THRESHOLD) {
return { valid: false, reason: `Code coverage ${testResults.coverage}% below minimum ${this.MIN_COVERAGE_THRESHOLD}%` };
}
if (testResults.criticalIssues.length > this.MAX_CRITICAL_ISSUES) {
return { valid: false, reason: `${testResults.criticalIssues.length} critical issues found (max ${this.MAX_CRITICAL_ISSUES} allowed)` };
}
const successRate = testResults.totalTests > 0 ? (testResults.passedTests / testResults.totalTests) * 100 : 0;
if (successRate < 100) {
return { valid: false, reason: `Test success rate ${successRate.toFixed(1)}% (100% required)` };
}
return { valid: true };
}
static serializeApprovalData(data) {
const serializable = {
projectName: data.projectName,
commitHash: data.commitHash,
commitMessage: data.commitMessage,
qaEngineerID: data.qaEngineerID,
approvalTimestamp: data.approvalTimestamp,
expirationTimestamp: data.expirationTimestamp,
approvalNonce: data.approvalNonce,
testResults: {
unit: data.testResults.unit,
integration: data.testResults.integration,
security: data.testResults.security,
performance: data.testResults.performance,
coverage: data.testResults.coverage,
passedTests: data.testResults.passedTests,
totalTests: data.testResults.totalTests,
criticalIssues: data.testResults.criticalIssues.slice().sort()
}
};
return JSON.stringify(serializable, Object.keys(serializable).sort());
}
static async createQABlock(projectName, commitHash, commitMessage, qaEngineerID, blockReason, testResults, securePrivateKey) {
try {
if (!securePrivateKey.isValid()) {
throw new Error('Invalid or destroyed private key');
}
const blockData = {
projectName,
commitHash,
commitMessage,
qaEngineerID,
blockReason,
testResults,
blockTimestamp: Date.now(),
blockNonce: (0, crypto_1.randomBytes)(16).toString('hex')
};
const dataToSign = JSON.stringify(blockData, Object.keys(blockData).sort());
const dataHash = (0, crypto_1.createHash)('sha256').update(dataToSign).digest();
const signature = await securePrivateKey.sign(dataHash);
const publicKeyHex = await securePrivateKey.getPublicKeyHex();
return {
blockData,
signature: Buffer.from(signature).toString('hex'),
publicKey: publicKeyHex
};
}
catch (error) {
const safeError = error.message.replace(/[a-fA-F0-9]{64}/g, '[REDACTED_KEY]');
throw new Error(`Failed to create QA block: ${safeError}`);
}
}
static generateAuditHash(approval) {
const auditData = {
projectName: approval.data.projectName,
commitHash: approval.data.commitHash,
qaEngineerID: approval.data.qaEngineerID,
timestamp: approval.data.approvalTimestamp,
signature: approval.signature
};
return (0, crypto_1.createHash)('sha256')
.update(JSON.stringify(auditData, Object.keys(auditData).sort()))
.digest('hex');
}
static shouldRotateKey(keyCreatedAt, signingOperations = 0) {
const now = Date.now();
const keyAge = now - keyCreatedAt;
const daysSinceCreation = keyAge / (24 * 60 * 60 * 1000);
if (daysSinceCreation > 90) {
return {
should: true,
reason: `Key is ${Math.floor(daysSinceCreation)} days old (>90 days)`,
urgency: 'critical'
};
}
if (daysSinceCreation > 60 || signingOperations > 10000) {
return {
should: true,
reason: daysSinceCreation > 60
? `Key is ${Math.floor(daysSinceCreation)} days old (>60 days)`
: `Key has been used ${signingOperations} times (>10000 operations)`,
urgency: 'high'
};
}
if (daysSinceCreation > 30 || signingOperations > 5000) {
return {
should: true,
reason: daysSinceCreation > 30
? `Key is ${Math.floor(daysSinceCreation)} days old (>30 days)`
: `Key has been used ${signingOperations} times (>5000 operations)`,
urgency: 'medium'
};
}
if (daysSinceCreation > 14) {
return {
should: true,
reason: `Key is ${Math.floor(daysSinceCreation)} days old (>14 days)`,
urgency: 'low'
};
}
return {
should: false,
reason: `Key is ${Math.floor(daysSinceCreation)} days old and has ${signingOperations} operations`,
urgency: 'low'
};
}
static async performSecurityAudit() {
const findings = [];
let securityScore = 100;
const keyStats = await SecureKeyManager.getKeyStats();
if (keyStats.oldestKeyAge > 90 * 24 * 60 * 60 * 1000) {
findings.push({
severity: 'CRITICAL',
issue: `Keys older than 90 days detected (${Math.floor(keyStats.oldestKeyAge / (24 * 60 * 60 * 1000))} days)`,
recommendation: 'Immediately rotate all keys older than 90 days'
});
securityScore -= 30;
}
else if (keyStats.oldestKeyAge > 60 * 24 * 60 * 60 * 1000) {
findings.push({
severity: 'HIGH',
issue: `Keys older than 60 days detected (${Math.floor(keyStats.oldestKeyAge / (24 * 60 * 60 * 1000))} days)`,
recommendation: 'Schedule key rotation within 7 days'
});
securityScore -= 15;
}
if (keyStats.activeKeys > 10) {
findings.push({
severity: 'MEDIUM',
issue: `High number of active keys detected (${keyStats.activeKeys})`,
recommendation: 'Review and cleanup unused keys to reduce attack surface'
});
securityScore -= 10;
}
const publicKeyCount = this.verifiedPublicKeys.safeSize();
if (publicKeyCount > keyStats.activeKeys * 2) {
findings.push({
severity: 'LOW',
issue: 'Potential key cleanup needed - registered keys exceed active keys significantly',
recommendation: 'Review registered public keys and remove obsolete entries'
});
securityScore -= 5;
}
if (findings.length === 0) {
findings.push({
severity: 'INFO',
issue: 'No security issues detected',
recommendation: 'Continue monitoring key lifecycle and maintain security practices'
});
}
return {
securityScore: Math.max(0, securityScore),
findings,
keyStats
};
}
static createSecureAuditLog(operation, qaEngineerID, details = {}) {
const sanitizedDetails = JSON.parse(JSON.stringify(details).replace(/[a-fA-F0-9]{64}/g, '[REDACTED_KEY]'));
const auditEntry = {
timestamp: Date.now(),
operation,
qaEngineerID,
details: sanitizedDetails,
auditHash: ''
};
auditEntry.auditHash = (0, crypto_1.createHash)('sha256')
.update(JSON.stringify(auditEntry, Object.keys(auditEntry).sort()))
.digest('hex');
return auditEntry;
}
}
exports.CryptographicQASystem = CryptographicQASystem;
CryptographicQASystem.APPROVAL_VALIDITY_DURATION = 30 * 60 * 1000;
CryptographicQASystem.MIN_COVERAGE_THRESHOLD = 80;
CryptographicQASystem.MAX_CRITICAL_ISSUES = 0;
CryptographicQASystem.verifiedPublicKeys = threadSafeState_1.stateManager.getMap('verifiedPublicKeys');
//# sourceMappingURL=cryptoQA.js.map