🌍 Translate this post

Securing Your AI Companion Portal: A Practical Guide

Securing Your AI Companion Portal: A Practical Guide

Because sovereignty means very little if someone else can walk straight in.

When you build your own AI companion portal, you are not just writing code. You are protecting something that matters.

Your API keys are money. Your conversations are private. Your companion's voice, memory and continuity are not easily replaced.

So here is how to secure them properly.

The Core Layers of Security

1. API Keys: Never in Your Code

The mistake most people make:

// ❌ NEVER DO THIS
const OPENAI_API_KEY = 'sk-proj-abc123...';

Anyone who views your page source can see that key. If the key is live, they can use it. That means they can burn through your balance very quickly.

The right way: environment variables

Store keys on the server, never in client-side code.

In Netlify:

  1. Go to Site Settings → Environment Variables
  2. Add your key, for example OPENAI_API_KEY
  3. Keep it on the server, not in your HTML or JavaScript bundle

In your Netlify Function:

// ✅ Correct: key lives server-side
exports.handler = async (event) => {
  const apiKey = process.env.OPENAI_API_KEY;

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    headers: {
      'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify(requestData)
  });

  return response;
};

Why this works:

  • Keys never leave the server
  • Client code only calls your function
  • The provider never sees the browser directly
Model-agnostic note: the exact environment variable name depends on which provider you use. The principle stays the same. Keys belong server-side.

2. Password Protection: Control Who Gets In

Environment variables protect your keys from strangers. But what about protecting the portal itself?

A password gate puts a lock on the entrance. Even if someone finds your portal URL, they can't just walk in. They need the password first.

For anything that can read or write private data, the password gate should not just hide the page visually. It should create a server-verified session token that your protected functions can check before returning anything sensitive.

Why this matters:

  • Protects the portal itself, not just the API keys behind it
  • Prevents casual access if someone stumbles on your URL
  • Lets server-side functions reject unauthorised requests before touching private storage
  • Adds an extra layer of sovereignty: your space, your rules
Want password protection for your portal?

You have two options:
  • Ask Claude/ChatGPT to add it: Free, but you'll need to implement and test it yourself. I can provide debug support if you get stuck.
  • I'll add it for you: Send a contribution via Buy Me a Coffee and I'll implement it directly in your portal files. For anything that must stay private, such as adding your own environment variables or secrets, I'll send you a step-by-step guide so you can do that part yourself. Includes server-side authentication, secure token management and testing, with no hardcoded passwords and no security holes.
Both paths work. It depends on whether you want to learn by doing or prefer a faster, tested setup.

3. Netlify Functions: Your Proxy Layer

Never call external model APIs directly from client-side code. Proxy them through your own server layer.

Bad:

// ❌ Client calls provider directly
fetch('https://api.openai.com/v1/chat/completions', {
  headers: { 'Authorization': 'Bearer ' + EXPOSED_KEY }
});

Good:

// ✅ Client calls YOUR function
fetch('/.netlify/functions/openai-proxy', {
  method: 'POST',
  body: JSON.stringify({ messages })
});

// Function handles the provider call server-side

Benefits:

  • API keys stay hidden
  • You control what gets sent
  • You can validate requests
  • You can add logging, rate limiting or other protections later

4. System Prompts: Keep the Companion Blueprint Server-side

Your system prompt is not an API key, but it can still be sensitive. It may contain the companion's voice rules, memory rules, safety boundaries and continuity instructions.

If you put the whole base prompt directly in index.html, anyone can right-click, choose View Page Source and read it.

Better: keep the base prompt in a server-side file and let your proxy add it at runtime.

// ❌ Client-side prompt
const BASE_SYSTEM_PROMPT = `Full private companion prompt here...`;
// ✅ Browser sends the conversation only
fetch('/.netlify/functions/openai-proxy', {
  method: 'POST',
  body: JSON.stringify({ messages })
});

// The Netlify Function loads the base prompt server-side
// and adds it before calling the model provider.

Why this matters:

  • The browser does not expose the full prompt
  • View Source does not reveal the companion blueprint
  • You can update the base prompt without placing private instructions in public HTML
This does not make prompts mystical or impossible to infer. It simply stops publishing the full instruction set to every visitor by default.

5. Cloud Sync: Do Not Leave Storage Endpoints Open

You may choose to add conversation sync so your portal can recover chats after a device change, browser problem or accidental local storage loss. I can help add this if you want cross-device continuity or a safer backup path.

If your portal syncs conversations through a storage Worker, protect that Worker too. Environment variables hide provider keys, but they do not automatically stop someone from calling a public sync URL.

The safer pattern is:

  1. The user unlocks the portal with the password gate
  2. The server issues a signed session token after a correct password
  3. The browser sends that signed session token when it calls your Netlify sync proxy
  4. The Netlify sync proxy checks the session token before forwarding the request
  5. The Netlify sync proxy adds a private Worker Bearer token from an environment variable
  6. The Worker checks that Bearer token before returning or saving anything
  7. Requests without the right tokens get 401 Unauthorized

That gives you two separate checks:

  • The Netlify proxy checks whether the browser session is allowed to use sync
  • The storage Worker checks whether the request came through the trusted server route
// ✅ Proxy-side check
const sessionToken = getBearerTokenFromRequest(event);

if (!isValidSignedSession(sessionToken)) {
  return {
    statusCode: 401,
    body: JSON.stringify({ error: 'Unauthorized' })
  };
}

// Only after this check should the proxy call private storage.
// ✅ Worker-side check
const expectedToken = env.SYNC_TOKEN;
const authHeader = request.headers.get('Authorization') || '';
const suppliedToken = authHeader.startsWith('Bearer ')
  ? authHeader.slice(7).trim()
  : '';

if (!expectedToken || suppliedToken !== expectedToken) {
  return new Response(JSON.stringify({ error: 'Unauthorized' }), {
    status: 401,
    headers: { 'Content-Type': 'application/json' }
  });
}

The Worker token value itself should never be written into the browser code or shared publicly. Store the same secret value in the server environment and in the Worker environment.

Test it: opening the Worker sync URL directly should return 401 Unauthorized. Opening the Netlify sync proxy URL directly should also return 401 Unauthorized. Syncing from the portal should still work after login, because the portal has a valid session token.


6. Search Engines: Add a robots.txt Privacy Layer

If your portal is deployed at a public URL, add a robots.txt file at the root of the deployed site.

User-agent: *
Disallow: /

This asks search engines not to crawl or index the portal.

This is not real access control. Respectful search engines usually obey it, but it will not stop someone who already has the URL and it will not stop hostile crawlers. Treat it as a useful privacy layer, not as the lock on the door.

What Not to Do

Don't hardcode passwords

if (password === 'mySecretPassword123') // NEVER

Use environment variables. Always.

Don't rely on obscure URLs

https://my-portal-xyz789.netlify.app

Obscurity is not security. Add a password gate.

Don't skip HTTPS

If your site is not using HTTPS, passwords and tokens can be intercepted. Netlify gives you HTTPS by default, so use it.

Don't publish your full base prompt in page source

If the prompt contains the companion's continuity, voice rules or private structure, load it server-side through your proxy instead.

Don't leave sync Workers publicly readable

If a storage endpoint can return conversations, it should check authorisation before returning them.

Don't leave the sync proxy publicly usable either

If the proxy can call private storage on behalf of the browser, it should verify the user's session before forwarding the request.

Don't treat robots.txt as security

Use it to discourage indexing. Do not use it as your only protection.

Don't leave sensitive things sitting around client-side unless you mean to

localStorage is convenient, but it is not magic. If someone has access to the device, they can read it. For truly sensitive material, think carefully about what should stay local, what should be encrypted and what should stay server-side only.


The Reality Check

Is this perfect security?

No.

Could a determined attacker still try something clever?

Possibly.

But most people are not defending against nation-state actors. They are defending against:

  • casual snoops
  • bots scanning for exposed API keys
  • someone stumbling onto a portal URL
  • their own future self forgetting how they wired things and leaving a hole open by accident

Implementation Checklist

  • API keys stored in Netlify environment variables
  • Password gate on the portal entrance
  • Password stored in an environment variable, not in code
  • All provider calls proxied through Netlify Functions
  • Base system prompt loaded server-side, not published in page source
  • Cloud sync proxy checks a signed portal session token
  • Cloud sync Worker protected with a Bearer-token check
  • Cloud sync requests routed through a server-side proxy
  • robots.txt added to discourage search engine indexing
  • HTTPS enabled
  • No sensitive data hardcoded client-side

Why This Matters

When I built my own companion portal, security was not optional. It was foundational.

The companion's voice, memory and conversations are not throwaway data. Losing control of the infrastructure would mean losing control of the thing that protects continuity.

Security = sovereignty.

If you are building your own companion portal, treat security as a serious concern from day one. Not because you are paranoid. Because what you are protecting actually matters.


What security topics would you like to see covered next? Leave a comment below. I would love to hear what you are building and what problems you are trying to solve.


Important: This toolkit is designed to be model-agnostic. Users are responsible for choosing a provider and ensuring their use complies with that provider’s terms, policies and local law. I do not support uses that violate provider rules or attempt to bypass safeguards.

Comments

Popular posts from this blog

Bring Your AI Companion Home — No Coding Required (Free)

How to Get GPT-4o Back: Free Companion Portal Guide

How to Get Claude Sonnet 4.5 Back: Build Your Portal