BETA

Architecture

Technical Implementation

hacka.re is built as a pure client-side application using vanilla JavaScript, HTML, and CSS. This approach eliminates the need for a backend server aside from the necessary OpenAI-compatible API endpoint for GenAI model access.

Tool Calling Implementation Demo

Below is a demonstration of the tool calling implementation in action (2x speed):

hacka.re is Vibe-Coded

99%+ of hacka.re's code was created through LLM-assisted development using Claude 3.7 Sonnet. Learn more about how hacka.re is developed and how it can be extended.


The application communicates directly with your configured provider using your API key, which is stored in your browser's localStorage. All message processing, including markdown rendering and syntax highlighting, happens locally in your browser.


Few dependencies limits the attack surface and thus increases the resilince to various attacks. We only depend on `marked` for rendering markdown, `dompurify` to prevent cross-site scripting, `tweetnacl` for mininmal-complexity strong in-browser encryption, and `qrcode` to make qr codes out of these self-contained GPTs links. Everything else including all of the UI components and logic have been 99%+ vibe-coded using the Claude 3.7 Sonnet model. hacka.re is by design pretty bare-bones but allows for arbitrary expansion of purpose-specific features through further LLM-assisted development with limited time and effort investments.

// Example of the streaming implementation
const response = await fetch(this.chatEndpoint, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
    },
    body: JSON.stringify({
        model: this.currentModel,
        messages: apiMessages,
        stream: true
    })
});

// Process the stream
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let aiResponse = '';

while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    // Decode chunk and update UI in real-time
    const chunk = decoder.decode(value);
    // Process SSE format...
}

Modular Storage Architecture

hacka.re implements a modular storage architecture that separates concerns and improves maintainability. The storage system is divided into several specialized components:

  1. EncryptionService: Handles all encryption and decryption operations
  2. NamespaceService: Manages namespaces for storage isolation based on title/subtitle
  3. CoreStorageService: Provides the core storage operations with encryption support
  4. DataService: Implements specific data type operations (API keys, models, chat history, etc.)
  5. StorageService: Main entry point that coordinates all storage operations

This modular approach offers several benefits:

  • Improved code organization with clear separation of concerns
  • Better testability of individual components
  • Easier maintenance and future enhancements
  • More flexible encryption and namespace management
// Example of the modular storage architecture

// EncryptionService handles encryption/decryption
window.EncryptionService = (function() {
    // Encryption-related functions
    function encrypt(data, passphrase) {
        // Implementation of encryption
    }
    
    function decrypt(encryptedData, passphrase) {
        // Implementation of decryption
    }
    
    // Public API
    return {
        encrypt: encrypt,
        decrypt: decrypt,
        // Other encryption-related methods
    };
})();

// NamespaceService manages storage namespaces
window.NamespaceService = (function() {
    // Namespace management functions
    function getNamespace() {
        // Get current namespace based on title/subtitle
    }
    
    function getNamespacedKey(baseKey) {
        // Get namespaced key for storage
    }
    
    // Public API
    return {
        getNamespace: getNamespace,
        getNamespacedKey: getNamespacedKey,
        // Other namespace-related methods
    };
})();

// CoreStorageService provides basic storage operations
window.CoreStorageService = (function() {
    // Core storage functions
    function setValue(baseKey, value) {
        // Set value with optional encryption
    }
    
    function getValue(baseKey, legacyKey, migrateFn) {
        // Get value with optional decryption
    }
    
    // Public API
    return {
        setValue: setValue,
        getValue: getValue,
        // Other storage-related methods
    };
})();

// DataService implements specific data operations
window.DataService = (function() {
    // Data-specific functions
    function saveApiKey(apiKey) {
        CoreStorageService.setValue(STORAGE_KEYS.API_KEY, apiKey);
    }
    
    function getApiKey() {
        return CoreStorageService.getValue(
            STORAGE_KEYS.API_KEY, 
            STORAGE_KEYS.API_KEY, 
            saveApiKey
        );
    }
    
    // Public API
    return {
        saveApiKey: saveApiKey,
        getApiKey: getApiKey,
        // Other data-specific methods
    };
})();

// StorageService is the main entry point
window.StorageService = (function() {
    // Initialize the core storage service
    CoreStorageService.init();
    
    // Public API - expose all data service methods
    return {
        // Namespace methods
        getNamespace: NamespaceService.getNamespace,
        getNamespacedKey: NamespaceService.getNamespacedKey,
        
        // Data methods
        saveApiKey: DataService.saveApiKey,
        getApiKey: DataService.getApiKey,
        // Other methods
    };
})();

Comprehensive Secure Sharing - Technical Details

hacka.re includes a feature to securely share various aspects of your configuration with others through session key-protected URL-based sharing. Note that links created with the share feature are susceptible to brute force attacks and rely entirely on a strong password- or session key to be resilient. The 12 random alphanumerical characters produced by default should be strong enough for most real-world applications but could of course be made arbitrarily stronger simply by increasing the number of rounds used for key derivation at the expense of compute and application responsiveness.


Comprehensive sharing options:

  1. API Key: Share your API key for access to models
  2. System Prompt: Share your custom system prompt for consistent AI behavior
  3. Active Model: Share your selected model preference with automatic fallback if unavailable
  4. Conversation Data: Share recent conversation history with configurable message count

How it works:

  1. When you create a shareable link, you select what to include (base URL, API key, model, system prompt, conversation data)
  2. A real-time link length indicator shows the estimated size of the generated link
  3. You can generate a strong random session key or provide your own
  4. Your selected data is encrypted using a key derived from your session key with cryptographically sound methods
  5. Only the encrypted data is included in the URL after the # symbol (the encryption key is NOT included)
  6. When someone opens the link, they're prompted to enter the session key/password
  7. The application derives the decryption key from the entered session key/password and attempts to decrypt the data
  8. If successful, the data is applied to their session and the URL is cleared from the browser history
  9. For model preferences, the system verifies availability with the recipient's API key and falls back gracefully if needed
  10. If unsuccessful (wrong session key/password), they're prompted to try again

Team collaboration with session keys:

  1. Teams can agree on a common session key to use for sharing links
  2. Each team member can enter and lock this session key in their sharing settings
  3. When a team member receives a link created with the team's session key, the system automatically tries the locked session key first
  4. If the session key works, the shared data is applied without prompting for the session key
  5. This allows seamless sharing among team members without repeatedly entering the same session key
  6. The session key should be shared through a secure channel separate from the links themselves

Technical implementation: The feature uses TweetNaCl.js (a JavaScript port of the NaCl crypto library) for the encryption, combined with session key-based key derivation:

// Simplified version of the comprehensive sharing implementation

// Generate a strong random alphanumeric session key
function generateStrongSessionKey() {
    const length = 12; // Fixed length of 12 characters
    const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    let password = "";

    // Get cryptographically strong random values
    const randomValues = new Uint8Array(length);
    window.crypto.getRandomValues(randomValues);

    // Convert to session key characters
    for (let i = 0; i < length; i++) {
        password += charset[randomValues[i] % charset.length];
    }

    return password;
}

// Create a comprehensive shareable link with selected data
function createComprehensiveShareableLink(options, sessionKey) {
    // Create a payload object with only the selected data
    const payload = {};

    // Include API key if selected
    if (options.includeApiKey && options.apiKey) {
        payload.apiKey = options.apiKey;
    }

    // Include system prompt if selected
    if (options.includeSystemPrompt && options.systemPrompt) {
        payload.systemPrompt = options.systemPrompt;
    }

    // Include active model if selected
    if (options.includeModel && options.model) {
        payload.model = options.model;
    }

    // Include conversation data if selected
    if (options.includeConversation && options.messages && options.messages.length > 0) {
        // Include only the specified number of most recent messages
        const messageCount = options.messageCount || 1;
        const startIndex = Math.max(0, options.messages.length - messageCount);
        payload.messages = options.messages.slice(startIndex);
    }

    // Encrypt the payload using the session key
    const encryptedData = encryptData(payload, sessionKey);

    // Create URL with only the encrypted data (no key)
    return `${window.location.href.split('#')[0]}#shared=${encryptedData}`;
}

// Encrypt data with a session key
function encryptData(payloadObj, sessionKey) {
    // Convert to JSON string
    const jsonString = JSON.stringify(payloadObj);
    const plain = nacl.util.decodeUTF8(jsonString);

    // Generate salt and derive key
    const salt = nacl.randomBytes(SALT_LENGTH);
    const key = deriveSeed(sessionKey, salt);

    // Generate nonce for secretbox
    const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);

    // Encrypt with secretbox (symmetric encryption)
    const cipher = nacl.secretbox(plain, nonce, key);

    // Combine salt, nonce, and cipher
    const fullMessage = new Uint8Array(
        salt.length +
        nonce.length +
        cipher.length
    );

    let offset = 0;
    fullMessage.set(salt, offset);
    offset += salt.length;

    fullMessage.set(nonce, offset);
    offset += nonce.length;

    fullMessage.set(cipher, offset);

    // Convert to base64 for URL-friendly format
    return nacl.util.encodeBase64(fullMessage);
}

// Extract and decrypt shared data from the URL
function extractSharedData(sessionKey) {
    try {
        // Get the hash fragment
        const hash = window.location.hash;

        // Check if it contains shared data
        if (hash.includes('#shared=')) {
            // Extract the encrypted data
            const encryptedData = hash.split('#shared=')[1];

            if (!encryptedData) {
                return null;
            }

            // Decrypt the data
            const data = decryptData(encryptedData, sessionKey);

            if (!data) {
                return null;
            }

            // Return all decrypted data
            return {
                apiKey: data.apiKey,
                systemPrompt: data.systemPrompt || null,
                model: data.model || null,
                messages: data.messages || null
            };
        }

        return null;
    } catch (error) {
        console.error('Error extracting shared data:', error);
        return null;
    }
}

Security considerations:

  • True session key-based encryption: The encryption key is derived from the session key and is never included in the URL
  • Salt-based key derivation: A unique salt is generated for each link, protecting against rainbow table attacks
  • Multiple hashing iterations: The key derivation process uses multiple iterations to increase security
  • URL fragment (#) usage: The data after # is not sent to servers when requesting a page, providing protection against server logging
  • Session key confirmation: When creating a link, you must confirm your session key to prevent typos
  • Intended for trusted sharing: Still only share these links with people you trust, as they will have full access to your API provider account once they enter the correct session key
  • Temporary usage: Consider revoking your API key after sharing if you're concerned about unauthorized access

When to use each sharing option:

  • API Key: Essential for giving someone access to your API provider account
  • System Prompt: Share your custom instructions to ensure consistent AI behavior
  • Active Model: Share your preferred model selection for consistent results
  • Conversation Data: Share context from your current conversation to continue a discussion

QR code generation:

  1. After generating a shareable link, a QR code is automatically created for easy mobile sharing
  2. The QR code encodes the complete shareable link including the encrypted data
  3. The system monitors the link length and provides warnings when approaching QR code capacity limits
  4. Standard QR codes can typically handle up to 1500-2000 bytes of data
  5. When links exceed this limit (common with large system prompts or conversation history), a warning is displayed
  6. The QR code uses error correction level L (low) to maximize data capacity
  7. Recipients can scan the QR code with any standard QR code scanner to open the link
  8. They will still need the session key to decrypt the data