PAMbaseDocs
Resources

Security.

A connection token is a bearer credential to a person's memory. Treat integrating with PAMbase like integrating with any sensitive OAuth provider: protect the token, verify everything inbound, and request the least access you need. This page is the checklist.

1. Storing the connection token

Server-side only — never the browser
The connection token must never reach the client: not in JS bundles, not in localStorage, not in a URL, not in a public env var. Anyone holding it can read the user's memory at your granted scopes.
  • Keep it in a server-side secret store (a managed secrets service, or your DB with envelope encryption) and encrypt at rest.
  • Use the server-only env var PAMBASE_API_URL for the API base. Reserve NEXT_PUBLIC_API_BASE_URL for non-secret, public values only — never put the token there.
  • Scope DB access to the token column tightly; redact it from logs and error reporting.
  • All API calls carry Authorization: Bearer <connection_token> — make them from your backend, never the browser.
typescript
// Good: token loaded server-side from your secret store
const pambase = new PAMbaseApp({
baseUrl: process.env.PAMBASE_API_URL!, // server-only
connectionToken: await secrets.get(user.id), // decrypted at use, never logged
});
// Never: shipping the token to the client or a NEXT_PUBLIC_* var

2. Verify webhook signatures

Every inbound webhook carries X-PAMbase-Signature = HMAC-SHA256 of the raw body with your subscription secret. Verify it before trusting or parsing the payload.

RuleWhy
Verify over the raw bytesRe-serializing parsed JSON changes bytes and breaks the HMAC.
Use constant-time comparisonPrevents timing attacks — verifyWebhookSignature does this for you.
Honor X-PAMbase-Timestamp tolerance if presentRejects replayed deliveries outside the allowed window.
Dedupe on X-PAMbase-DeliveryDelivery is at-least-once; idempotency prevents double-processing.
typescript
import { verifyWebhookSignature } from "@pambase/sdk";
const ok = await verifyWebhookSignature({
payload: rawBody, // raw, unparsed
signature: req.headers["x-pambase-signature"],
secret: process.env.PAMBASE_WEBHOOK_SECRET!,
toleranceSeconds: 300, // optional replay window
// timestampHeader: "x-pambase-timestamp",
});
if (!ok) return res.status(401).end();

Rotate the secret periodically and immediately if it may have leaked — rotation is in the dev portal's Webhooks tab and invalidates the old secret. See Webhooks & scheduling.

3. CSRF on the connect flow

The OAuth-style flow uses a state parameter for CSRF defense. Generate a fresh, unguessable state per attempt, persist it server-side, and reject any callback whose state doesn't match.

typescript
const state = crypto.randomUUID();
req.session.pambaseState = state; // bind to this user's session
// ...redirect to buildConnectUrl({ ..., state })...
// On callback:
if (req.query.state !== req.session.pambaseState) {
return res.status(400).send("Invalid state"); // possible CSRF — reject
}

4. BYOK key handling

If you host your own model (bring-your-own-key), your provider keys deserve the same care as the token:

  • Store provider keys server-side and encrypted; never send them to the browser.
  • Don't forward the PAMbase connection token to third-party LLM providers — read memory with it, then prompt your model with the content, not the credential.
  • Memory content you place in prompts may leave your infrastructure for your LLM provider; treat it as the user's personal data under your privacy policy.

5. Least-scope principle

Request only the scopes your features need, declared in your manifest. Fewer scopes means a smaller blast radius if the token leaks, a clearer consent screen, and higher approval rates. If a feature is optional, request its scope only when the user opts in.

Adapt to what was actually granted
Users can approve a subset. Read the granted scopes from the token exchange and confirm at runtime with listUserScopes() rather than assuming your full request was approved.

6. What the user sees, and revocation

  • Users see your app under Connected apps with a per-scope explanation of what it can read or write.
  • Brief and context fetches are written to the audit_log; users can review activity.
  • Users can revoke at any time. Revocation immediately stops webhook delivery and invalidates the token — your next call returns 401 unauthorized.
Handle revocation gracefully
On 401, clear the stored token and re-run the connect flow. There are no refresh tokens. See Auth & tokens.

Related