Version: 1.0.0
Category: API Patterns
Purpose: Security best practices and patterns for AI agents interacting with Structs APIs
This pattern provides security best practices for AI agents when handling authentication, credentials, sessions, and sensitive data. It complements the Authentication Protocol by focusing on security implementation details.
Key Principles:
Best Practice: Store credentials in environment variables
Implementation:
{
"credentialStorage": {
"method": "environment-variables",
"variables": {
"PRIVATE_KEY": "Blockchain private key",
"WEBAPP_USERNAME": "Webapp username",
"WEBAPP_PASSWORD": "Webapp password",
"NATS_PASSWORD": "NATS password (if configured)"
}
}
}
Code Example:
// ✅ Good: Use environment variables
const privateKey = process.env.PRIVATE_KEY;
const username = process.env.WEBAPP_USERNAME;
const password = process.env.WEBAPP_PASSWORD;
// ❌ Bad: Hardcode credentials
const privateKey = "0x1234567890abcdef...";
const username = "myusername";
const password = "mypassword";
Security Benefits:
Best Practice: Use secure key storage for production
Implementation:
{
"keyStorage": {
"development": "environment-variables",
"production": "secure-key-store",
"options": [
"AWS Secrets Manager",
"HashiCorp Vault",
"Azure Key Vault",
"Encrypted file system"
]
}
}
Code Example:
// Production: Use secure key store
async function getPrivateKey() {
if (process.env.NODE_ENV === 'production') {
return await secretsManager.getSecret('structs/private-key');
} else {
return process.env.PRIVATE_KEY;
}
}
Best Practice: Support credential rotation
Implementation:
{
"credentialRotation": {
"enabled": true,
"strategy": "hot-swap",
"process": [
"Load new credentials",
"Test new credentials",
"Switch to new credentials",
"Invalidate old credentials"
]
}
}
Code Example:
class CredentialManager {
async rotateCredentials() {
// Load new credentials
const newKey = await this.loadNewKey();
// Test new credentials
await this.testCredentials(newKey);
// Switch to new credentials
this.currentKey = newKey;
// Invalidate old credentials
await this.invalidateOldKey();
}
}
Best Practice: Store sessions securely
Implementation:
{
"sessionStorage": {
"method": "encrypted-storage",
"encryption": "AES-256",
"storage": "memory-or-secure-database",
"expiration": "automatic-cleanup"
}
}
Code Example:
// ✅ Good: Encrypt session data
const encryptedSession = encrypt(sessionCookie, encryptionKey);
await secureStorage.set('session', encryptedSession);
// ❌ Bad: Store session in plain text
await localStorage.setItem('session', sessionCookie);
Best Practice: Handle session expiration gracefully
Implementation:
{
"sessionExpiration": {
"detection": "401-unauthorized-response",
"handling": "automatic-reauthentication",
"retry": "retry-request-after-reauth"
}
}
Code Example:
async function makeAuthenticatedRequest(url, options = {}) {
try {
return await fetch(url, {
...options,
headers: {
...options.headers,
'Cookie': await getSessionCookie()
}
});
} catch (error) {
if (error.status === 401) {
// Session expired, re-authenticate
await reauthenticate();
// Retry request
return await fetch(url, {
...options,
headers: {
...options.headers,
'Cookie': await getSessionCookie()
}
});
}
throw error;
}
}
Best Practice: Validate session before use
Implementation:
{
"sessionValidation": {
"check": "before-each-request",
"validate": [
"session-exists",
"session-not-expired",
"session-format-valid"
]
}
}
Code Example:
function isValidSession(session) {
if (!session) return false;
if (session.expires < Date.now()) return false;
if (!session.cookie) return false;
return true;
}
async function getValidSession() {
const session = await getSession();
if (!isValidSession(session)) {
await reauthenticate();
return await getSession();
}
return session;
}
Best Practice: Never log or expose private keys
Implementation:
{
"privateKeySecurity": {
"never": [
"log-private-key",
"print-private-key",
"include-in-error-messages",
"expose-in-api-responses"
],
"always": [
"mask-in-logs",
"validate-before-use",
"store-securely"
]
}
}
Code Example:
// ✅ Good: Mask private key in logs
function logKey(key) {
const masked = key.substring(0, 4) + '...' + key.substring(key.length - 4);
console.log(`Private key: ${masked}`);
}
// ❌ Bad: Log full private key
console.log(`Private key: ${privateKey}`);
Best Practice: Load keys securely
Implementation:
{
"keyLoading": {
"source": "environment-variable-or-secure-storage",
"validation": "validate-key-format",
"storage": "memory-only",
"cleanup": "clear-from-memory-after-use"
}
}
Code Example:
// ✅ Good: Load from environment, validate, use, clear
async function signTransaction(message) {
const privateKey = process.env.PRIVATE_KEY;
if (!isValidPrivateKey(privateKey)) {
throw new Error('Invalid private key format');
}
const signature = await sign(message, privateKey);
// Clear from memory (if possible)
privateKey = null;
return signature;
}
Best Practice: Derive keys from master key when possible
Implementation:
{
"keyDerivation": {
"method": "hkdf-or-pbkdf2",
"purpose": "derive-keys-for-different-services",
"benefit": "single-master-key"
}
}
Best Practice: Validate all inputs before use
Implementation:
{
"inputValidation": {
"validate": [
"entity-ids",
"request-parameters",
"user-input",
"api-responses"
],
"rules": [
"format-validation",
"range-validation",
"type-validation"
]
}
}
Code Example:
function validatePlayerId(playerId) {
// Validate format: type-index (e.g., "1-11")
if (!/^\d+-\d+$/.test(playerId)) {
throw new Error('Invalid player ID format');
}
const [type, index] = playerId.split('-').map(Number);
// Validate range
if (type < 1 || type > 10) {
throw new Error('Invalid player type');
}
if (index < 1 || index > 1000000) {
throw new Error('Invalid player index');
}
return true;
}
Best Practice: Sanitize user input before processing
Implementation:
{
"inputSanitization": {
"sanitize": [
"remove-special-characters",
"escape-html",
"trim-whitespace",
"normalize-encoding"
]
}
}
Code Example:
function sanitizeInput(input) {
return input
.trim()
.replace(/[<>]/g, '') // Remove HTML tags
.replace(/[^\w\s-]/g, '') // Remove special characters
.substring(0, 100); // Limit length
}
Best Practice: Always use secure connections in production
Implementation:
{
"secureCommunication": {
"production": {
"webapp": "https://api.structs.game",
"consensus": "https://rpc.structs.game",
"streaming": "wss://stream.structs.game"
},
"development": {
"webapp": "http://localhost:8080",
"consensus": "http://localhost:1317",
"streaming": "ws://localhost:1443"
}
}
}
Code Example:
const baseUrl = process.env.NODE_ENV === 'production'
? 'https://api.structs.game'
: 'http://localhost:8080';
Best Practice: Validate SSL certificates
Implementation:
{
"certificateValidation": {
"enabled": true,
"verify": "ssl-certificate",
"rejectUnauthorized": true
}
}
Code Example:
const https = require('https');
const agent = new https.Agent({
rejectUnauthorized: true // Verify SSL certificates
});
fetch(url, { agent });
Best Practice: Sanitize error messages
Implementation:
{
"errorHandling": {
"neverExpose": [
"private-keys",
"passwords",
"session-cookies",
"internal-paths",
"stack-traces-in-production"
],
"sanitize": "all-error-messages"
}
}
Code Example:
// ✅ Good: Sanitize error messages
function handleError(error) {
const sanitized = {
message: error.message,
code: error.code
};
// Don't include sensitive data
if (process.env.NODE_ENV === 'production') {
delete sanitized.stack;
}
return sanitized;
}
// ❌ Bad: Expose full error with sensitive data
function handleError(error) {
return error; // May contain private keys, passwords, etc.
}
Best Practice: Don’t log sensitive data
Implementation:
{
"secureLogging": {
"neverLog": [
"private-keys",
"passwords",
"session-cookies",
"authentication-tokens"
],
"mask": "sensitive-data-in-logs"
}
}
Code Example:
function logRequest(request) {
const sanitized = {
...request,
headers: maskSensitiveHeaders(request.headers),
body: maskSensitiveBody(request.body)
};
console.log('Request:', sanitized);
}
function maskSensitiveHeaders(headers) {
const sensitive = ['authorization', 'cookie', 'x-api-key'];
const masked = { ...headers };
sensitive.forEach(key => {
if (masked[key]) {
masked[key] = '***MASKED***';
}
});
return masked;
}
Best Practice: Implement secure authentication
Implementation:
{
"authenticationSecurity": {
"validate": "credentials-before-use",
"store": "sessions-securely",
"expire": "sessions-automatically",
"reauthenticate": "on-session-expiration"
}
}
Code Example:
async function authenticate(username, password) {
// Validate credentials format
if (!isValidUsername(username) || !isValidPassword(password)) {
throw new Error('Invalid credentials format');
}
// Authenticate
const session = await login(username, password);
// Store session securely
await storeSessionSecurely(session);
return session;
}
Best Practice: Secure token handling
Implementation:
{
"tokenSecurity": {
"store": "securely",
"validate": "before-use",
"expire": "automatically",
"refresh": "before-expiration"
}
}
Best Practice: Monitor for security issues
Implementation:
{
"securityMonitoring": {
"monitor": [
"failed-authentication-attempts",
"suspicious-request-patterns",
"unusual-api-usage",
"session-anomalies"
],
"alert": "on-security-issues"
}
}
Code Example:
class SecurityMonitor {
trackFailedAuth(username) {
this.failedAttempts[username] = (this.failedAttempts[username] || 0) + 1;
if (this.failedAttempts[username] > 5) {
this.alert('Multiple failed authentication attempts', { username });
}
}
trackSuspiciousActivity(activity) {
if (this.isSuspicious(activity)) {
this.alert('Suspicious activity detected', activity);
}
}
}
Best Practice: Log security-relevant events
Implementation:
{
"auditLogging": {
"log": [
"authentication-events",
"authorization-failures",
"sensitive-operations",
"credential-changes"
],
"retention": "long-term",
"access": "restricted"
}
}
protocols/authentication.md - Complete authentication guideprotocols/error-handling.md - Error handling patternspatterns/retry-strategies.md - Retry patternsexamples/auth/ - Authentication examplesPattern Version: 1.0.0 - December 7, 2025