release: prepare v4.8.5 security hardening release
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled

This commit is contained in:
lockbitchat
2026-05-17 14:48:52 -04:00
parent 4b8c8829f1
commit 0a42aa13c3
35 changed files with 2975 additions and 11976 deletions
+112 -15
View File
@@ -36,6 +36,31 @@
// Verification Component
const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => {
const [sasInput, setSasInput] = React.useState('');
const [error, setError] = React.useState('');
const normalizedExpectedLength = (verificationCode || '').replace(/[-\s]/g, '').length;
const normalizedInputLength = sasInput.replace(/[-\s]/g, '').length;
const canConfirm = !localConfirmed && normalizedExpectedLength > 0 && normalizedInputLength === normalizedExpectedLength;
React.useEffect(() => {
setSasInput('');
setError('');
}, [verificationCode]);
const handleConfirm = async () => {
try {
setError('');
await onConfirm(sasInput);
} catch (confirmationError) {
setSasInput('');
if (confirmationError?.message === 'SAS_MAX_ATTEMPTS') {
setError('Too many incorrect attempts. Session reset for safety.');
} else {
setError('Incorrect code. Check it with your peer and try again.');
}
}
};
return React.createElement('div', {
className: "card-minimal rounded-xl p-6 border-purple-500/20"
}, [
@@ -63,7 +88,7 @@
React.createElement('p', {
key: 'description',
className: "text-secondary text-sm"
}, "Verify the security code with your contact via another communication channel (voice, SMS, etc.):"),
}, "Compare this code with your peer out-of-band, then type the same code below to unlock the chat."),
React.createElement('div', {
key: 'code-display',
className: "text-center"
@@ -73,6 +98,36 @@
className: "verification-code text-2xl py-4"
}, verificationCode)
]),
React.createElement('div', {
key: 'sas-input-wrap',
className: "space-y-2"
}, [
React.createElement('label', {
key: 'sas-label',
className: "block text-sm text-secondary"
}, "Enter the verified code"),
React.createElement('input', {
key: 'sas-input',
type: 'text',
value: sasInput,
onChange: (event) => {
setSasInput(event.target.value.toUpperCase());
if (error) setError('');
},
autoFocus: true,
autoComplete: 'off',
spellCheck: false,
inputMode: 'text',
disabled: localConfirmed,
placeholder: verificationCode ? 'Type code here' : 'Waiting for code…',
className: "w-full rounded-lg border border-purple-500/30 bg-black/20 px-4 py-3 text-center text-xl tracking-[0.3em] text-primary uppercase focus:border-purple-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60",
style: { fontFamily: 'monospace', textTransform: 'uppercase' }
}),
error && React.createElement('p', {
key: 'sas-error',
className: "text-sm text-red-400"
}, error)
]),
// Verification status indicators
React.createElement('div', {
key: 'verification-status',
@@ -142,14 +197,14 @@
}, [
React.createElement('button', {
key: 'confirm',
onClick: onConfirm,
disabled: localConfirmed,
className: `flex-1 py-3 px-4 rounded-lg font-medium transition-all duration-200 ${localConfirmed ? 'bg-gray-500/20 text-gray-400 cursor-not-allowed' : 'btn-verify text-white'}`
onClick: handleConfirm,
disabled: !canConfirm,
className: `flex-1 py-3 px-4 rounded-lg font-medium transition-all duration-200 ${!canConfirm ? 'bg-gray-500/20 text-gray-400 cursor-not-allowed' : 'btn-verify text-white'}`
}, [
React.createElement('i', {
className: `fas ${localConfirmed ? 'fa-check-circle' : 'fa-check'} mr-2`
}),
localConfirmed ? 'Confirmed' : 'The codes match'
localConfirmed ? 'Confirmed' : 'Confirm code'
]),
React.createElement('button', {
key: 'reject',
@@ -283,7 +338,9 @@
markAnswerCreated,
notificationIntegrationRef,
isGeneratingKeys,
handleCreateOffer
handleCreateOffer,
relayOnlyMode,
setRelayOnlyMode
}) => {
const [mode, setMode] = React.useState('select');
const [notificationPermissionRequested, setNotificationPermissionRequested] = React.useState(false);
@@ -294,12 +351,12 @@
onClearData();
};
const handleVerificationConfirm = () => {
onVerifyConnection(true);
const handleVerificationConfirm = (userCode) => {
return onVerifyConnection(userCode);
};
const handleVerificationReject = () => {
onVerifyConnection(false);
onVerifyConnection(null, false);
};
// Request notification permission on first user interaction
@@ -449,6 +506,28 @@
className: "text-secondary max-w-2xl mx-auto"
}, "Choose a connection method for a secure channel with ECDH encryption and Perfect Forward Secrecy.")
]),
React.createElement('label', {
key: 'privacy-mode',
className: "mb-6 mx-auto flex max-w-2xl items-start gap-3 rounded-xl border border-purple-500/20 bg-purple-500/10 p-4 text-left"
}, [
React.createElement('input', {
key: 'input',
type: 'checkbox',
checked: relayOnlyMode,
onChange: (event) => setRelayOnlyMode(event.target.checked),
className: "mt-1"
}),
React.createElement('span', { key: 'copy' }, [
React.createElement('span', {
key: 'title',
className: "block text-sm font-medium text-primary"
}, 'Privacy mode: relay-only WebRTC'),
React.createElement('span', {
key: 'desc',
className: "block text-sm text-secondary"
}, 'Uses TURN relay-only when configured. Without TURN, direct WebRTC may expose IP addresses and relay-only connections cannot start.')
])
]),
React.createElement('div', {
key: 'options',
@@ -1464,6 +1543,9 @@
const [messages, setMessages] = React.useState([]);
const [connectionStatus, setConnectionStatus] = React.useState('disconnected');
const [relayOnlyMode, setRelayOnlyMode] = React.useState(() => {
try { return localStorage.getItem('securebit_relay_only_mode') === 'true'; } catch { return false; }
});
// Moved scrollToBottom logic to be available globally
const [messageInput, setMessageInput] = React.useState('');
@@ -1680,6 +1762,13 @@
// Create scroll function using global helper
const scrollToBottom = createScrollToBottomFunction(chatMessagesRef);
React.useEffect(() => {
try { localStorage.setItem('securebit_relay_only_mode', String(relayOnlyMode)); } catch {}
if (webrtcManagerRef.current?._config?.webrtc) {
webrtcManagerRef.current._config.webrtc.relayOnly = relayOnlyMode;
}
}, [relayOnlyMode]);
// Auto-scroll when messages change
React.useEffect(() => {
@@ -1909,7 +1998,13 @@
handleKeyExchange,
handleVerificationRequired,
handleAnswerError,
handleVerificationStateChange
handleVerificationStateChange,
{
webrtc: {
relayOnly: relayOnlyMode,
iceServers: Array.isArray(window.SECUREBIT_ICE_SERVERS) ? window.SECUREBIT_ICE_SERVERS : undefined
}
}
);
// Initialize notification integration if permission was already granted
@@ -1926,7 +2021,7 @@
}
}
handleMessage(' SecureBit.chat Enhanced Security Edition v4.7.56 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
handleMessage(' SecureBit.chat Enhanced Security Edition v4.8.5 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
const handleBeforeUnload = (event) => {
if (event.type === 'beforeunload' && !isTabSwitching) {
@@ -3226,9 +3321,9 @@
}
};
const handleVerifyConnection = async (isValid) => {
const handleVerifyConnection = async (userCode, isValid = true) => {
if (isValid) {
webrtcManagerRef.current.confirmVerification();
webrtcManagerRef.current.confirmVerification(userCode);
// Mark local verification as confirmed
setLocalVerificationConfirmed(true);
@@ -3666,7 +3761,9 @@
markAnswerCreated: markAnswerCreated,
notificationIntegrationRef: notificationIntegrationRef,
isGeneratingKeys: isGeneratingKeys,
handleCreateOffer: handleCreateOffer
handleCreateOffer: handleCreateOffer,
relayOnlyMode: relayOnlyMode,
setRelayOnlyMode: setRelayOnlyMode
})
),
@@ -3810,4 +3907,4 @@
ReactDOM.render(AppWithUpdateChecker, document.getElementById('root'));
} else {
ReactDOM.render(React.createElement(EnhancedSecureP2PChat), document.getElementById('root'));
}
}