fix: Add strict ownership validation for claim modifications
Add comprehensive security checks to prevent unauthorized claim modifications while preserving legitimate owner's ability to update their own claims. - Add ownership verification before any pass.remove() operations - Validate claimKey format: claim:domain:claimant - Ensure claimant matches current user before allowing modifications - Create safeRemoveClaim() helper with multi-layer validation - Add security logging for audit trail - Fail-safe error handling prevents unauthorized claim interference Resolves critical data integrity issue where claims could be overwritten by unauthorized peers, while maintaining proper claim management capabilities.
This commit is contained in:
+51
-5
@@ -162,16 +162,29 @@ async function updateClaimClients(domain, claimant, clients) {
|
||||
|
||||
// Parse existing claim
|
||||
const parsed = parseClaimValue(existingClaim.value);
|
||||
|
||||
|
||||
// CRITICAL: Verify ownership before modifying
|
||||
const claimKeyParts = claimKey.split(':');
|
||||
if (claimKeyParts.length !== 3 || claimKeyParts[0] !== 'claim' || claimKeyParts[1] !== domain || claimKeyParts[2] !== claimant) {
|
||||
logError('Core', `SECURITY: Claim key ownership verification failed for ${claimKey}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the entry exists and belongs to us
|
||||
if (!existingClaim || existingClaim.key !== claimKey) {
|
||||
logWarn('Core', `Cannot update claim: entry not found or ownership mismatch`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update with new clients array
|
||||
const updatedValue = JSON.stringify({
|
||||
hash: parsed.hash,
|
||||
clients: clients,
|
||||
timestamp: parsed.timestamp || Date.now()
|
||||
});
|
||||
|
||||
// Remove old claim and add updated one
|
||||
await pass.remove(claimKey);
|
||||
|
||||
// SAFE: Remove old claim and add updated one (ownership verified)
|
||||
await safeRemoveClaim(claimKey, claimant);
|
||||
await pass.add(claimKey, updatedValue);
|
||||
|
||||
invalidateEntriesCache();
|
||||
@@ -745,6 +758,38 @@ async function removeOwnClaimAndVotes(domain, localWriter) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for safe claim removal with ownership verification
|
||||
async function safeRemoveClaim(claimKey, expectedClaimant) {
|
||||
// Parse and validate claimKey format
|
||||
const parts = claimKey.split(':');
|
||||
if (parts.length !== 3 || parts[0] !== 'claim') {
|
||||
throw new Error(`Invalid claim key format: ${claimKey}`);
|
||||
}
|
||||
|
||||
const domain = parts[1];
|
||||
const claimant = parts[2];
|
||||
|
||||
// CRITICAL: Verify the claimant matches expected
|
||||
if (claimant !== expectedClaimant) {
|
||||
logError('Core', `SECURITY: Attempted to remove claim with wrong claimant. Expected ${expectedClaimant}, got ${claimant}`);
|
||||
throw new Error(`Cannot remove claim: ownership mismatch`);
|
||||
}
|
||||
|
||||
// Verify entry exists before removing
|
||||
const allEntries = await getAllEntries(state.dnsPass, false);
|
||||
const entryExists = allEntries.some(e => e.key === claimKey);
|
||||
|
||||
if (!entryExists) {
|
||||
logWarn('Core', `Claim ${claimKey} does not exist, skipping remove`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safe to remove - ownership verified
|
||||
await state.dnsPass.remove(claimKey);
|
||||
logInfo('Core', `Safely removed claim ${claimKey} (owned by ${claimant})`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove all records (claims and votes) from the network
|
||||
async function removeAllRecords() {
|
||||
const pass = state.dnsPass;
|
||||
@@ -853,5 +898,6 @@ module.exports = {
|
||||
parseClaimValue,
|
||||
getClaimClients,
|
||||
updateClaimClients,
|
||||
cleanupRedundantClientEntries
|
||||
cleanupRedundantClientEntries,
|
||||
safeRemoveClaim
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const state = require('../infrastructure/state');
|
||||
const { logDebug, logInfo } = require('../infrastructure/logger');
|
||||
const { getAllEntries, autoVoteForDomain, invalidateEntriesCache, parseClaimValue } = require('./core');
|
||||
const { getAllEntries, autoVoteForDomain, invalidateEntriesCache, parseClaimValue, safeRemoveClaim } = require('./core');
|
||||
const { trackDomainEvent } = require('../maintenance/metrics');
|
||||
const { getPersistentPublicKey } = require('../infrastructure/utils');
|
||||
// Initialize reserved IPs set if not already present
|
||||
@@ -35,21 +35,29 @@ async function addDomain(domain, hash, ssl = false) {
|
||||
const claimValue = JSON.stringify({ hash, timestamp, ssl: sslValue });
|
||||
|
||||
if (existingClaim) {
|
||||
// User already has a claim - update it instead of creating duplicate
|
||||
// User already has a claim - verify ownership before modifying
|
||||
const parsed = parseClaimValue(existingClaim.value);
|
||||
|
||||
|
||||
// CRITICAL: Verify the claimKey matches our claimant (double-check ownership)
|
||||
const claimKeyParts = existingClaim.key.split(':');
|
||||
if (claimKeyParts.length !== 3 || claimKeyParts[0] !== 'claim' || claimKeyParts[1] !== domain || claimKeyParts[2] !== claimant) {
|
||||
logError('Domains', `SECURITY: Claim key mismatch! Expected ${claimKey}, got ${existingClaim.key}. Refusing to modify.`);
|
||||
throw new Error(`Cannot modify claim: key ownership verification failed`);
|
||||
}
|
||||
|
||||
// Check if anything actually changed
|
||||
if (parsed.hash === hash && parsed.ssl === sslValue) {
|
||||
logDebug('Domains', `Claim for ${domain} already exists with same hash and SSL flag, skipping update`);
|
||||
return; // No changes needed
|
||||
}
|
||||
|
||||
|
||||
logInfo('Domains', `Updating existing claim for ${domain} (hash: ${parsed.hash} -> ${hash}, SSL: ${parsed.ssl} -> ${sslValue})`);
|
||||
|
||||
// Remove old claim and add updated one
|
||||
await pass.remove(claimKey);
|
||||
|
||||
// SAFE: Remove old claim and add updated one (ownership verified above)
|
||||
// Use the safe remove helper for consistent validation
|
||||
await safeRemoveClaim(claimKey, claimant);
|
||||
await pass.add(claimKey, claimValue);
|
||||
|
||||
|
||||
logInfo('Domains', `Updated claim ${claimKey} with new hash ${hash} and SSL=${sslValue}`);
|
||||
} else {
|
||||
// New claim - add it
|
||||
|
||||
Reference in New Issue
Block a user