Security best practices for implementing and using the Ad Context Protocol.
Critical for Production Use
AdCP handles financial commitments and potentially sensitive campaign data. Implementations managing real advertising budgets MUST implement the security controls outlined in this document.
Overview
AdCP operates in a high-stakes environment where:
- Financial transactions involve real advertising spend
- Multi-party trust requires coordination between Principals, Publishers, and Orchestrators
- Sensitive data includes first-party signals, pre-launch creatives, and competitive targeting strategies
- Asynchronous operations span multiple systems and protocols
This document provides security requirements and best practices for AdCP implementations.
Risk Classification
AdCP operations fall into three risk categories based on their potential impact:
High-Risk Operations (Financial)
These operations commit real advertising budgets and require the strongest authentication:
| Operation | Risk | Recommended Auth |
|---|
create_media_buy | Creates financial commitments | OAuth 2.0 or mTLS |
update_media_buy | Modifies budgets and campaign parameters | OAuth 2.0 or mTLS |
Requirements for High-Risk Operations:
- Implementations SHOULD use OAuth 2.0 with proper token validation or mutual TLS
- Bearer tokens alone provide weaker security guarantees (see Bearer Token Risks)
- Multi-factor authentication or approval workflows recommended for large budgets
- All requests must be logged with full audit trail
Medium-Risk Operations (Data Access)
These operations access sensitive business data:
| Operation | Risk | Recommended Auth |
|---|
get_media_buy_delivery | Exposes performance metrics and spend data | OAuth 2.0, API Key, or Bearer Token |
list_creatives | Access to creative assets | OAuth 2.0, API Key, or Bearer Token |
sync_creatives | Uploads potentially sensitive creative content | OAuth 2.0, API Key, or Bearer Token |
provide_performance_feedback | Submits optimization signals | OAuth 2.0, API Key, or Bearer Token |
get_signals | Access to audience data | OAuth 2.0, API Key, or Bearer Token |
activate_signal | Activates audience segments | OAuth 2.0, API Key, or Bearer Token |
Low-Risk Operations (Discovery)
These operations are publicly accessible to enable discovery:
| Operation | Risk | Auth Required |
|---|
get_products | Public inventory discovery | Optional (limited results without auth) |
list_creative_formats | Public format catalog | None |
list_authorized_properties | Public property listing | None |
Authentication & Authorization
AdCP does not mandate a specific authentication mechanism, allowing implementers to choose appropriate protocols for their use case. However, production implementations MUST enforce strong authentication and authorization.
Authentication Mechanisms
Recommended Approaches:
- OAuth 2.0: Industry standard, supports scoped permissions and delegation
- API Keys with HMAC: Simple, appropriate for server-to-server communication
- Mutual TLS (mTLS): Strong cryptographic authentication for high-security environments
- JWT Tokens: Stateless authentication with embedded claims
Implementation Requirements:
// Example: OAuth 2.0 token validation
interface AuthConfig {
tokenEndpoint: string;
requiredScopes: string[];
allowedIssuers: string[];
}
async function validateToken(token: string, config: AuthConfig): Promise<boolean> {
// Verify token signature
// Check expiration
// Validate required scopes
// Confirm issuer is trusted
return true; // or throw authentication error
}
Security Standards:
- Use cryptographically strong authentication mechanisms (minimum 128-bit security)
- Never transmit credentials in URL parameters (use headers or request bodies)
- Implement proper credential rotation procedures
- Support credential revocation and audit logging
Authorization Model
Scoped Permissions:
AdCP implementations should implement fine-grained authorization:
| Scope | Description | Example Operations |
|---|
read:products | Discover available inventory | get_products, list_creative_formats, list_authorized_properties |
read:campaigns | View campaign details | list_creatives, get_media_buy_delivery |
write:campaigns | Create and modify campaigns | create_media_buy, update_media_buy |
read:creatives | Preview and inspect creatives | preview_creative |
write:creatives | Upload and manage creatives | sync_creatives, build_creative |
read:signals | Access audience signals | get_signals (if signals module implemented) |
write:signals | Activate signals for targeting | activate_signal (if signals module implemented) |
admin:* | Full administrative access | All operations |
Principal Isolation:
- Each Principal MUST have separate credentials
- Principal A cannot access or modify Principal B’s campaigns
- Orchestrators managing multiple principals MUST enforce strict isolation
- Implement row-level security in multi-tenant deployments
Publisher Verification:
- Principals should verify publisher identity before committing budgets
- Use trusted publisher registries or reputation systems when available
- Log all publisher interactions for audit purposes
Credential Management
Storage Best Practices:
- Use secure key management systems (AWS KMS, Azure Key Vault, HashiCorp Vault)
- Never commit credentials to version control
- Use environment variables or secret managers for deployment
- Encrypt credentials at rest
Rotation Procedures:
- Rotate credentials every 90 days minimum
- Support graceful rotation (allow old and new credentials during transition)
- Revoke compromised credentials immediately
- Maintain audit log of all credential changes
For Orchestrators:
// Example: Secure credential storage for multi-principal orchestrator
interface PrincipalCredentials {
principalId: string;
encryptedApiKey: string; // Encrypted at rest
scopes: string[];
expiresAt: Date;
rotationScheduled: Date;
}
class CredentialManager {
async getCredentials(principalId: string): Promise<string> {
// Retrieve encrypted credentials
// Decrypt using KMS
// Check expiration
// Return plaintext for use (never log!)
}
}
Transport Security
HTTPS Requirements:
- All AdCP communications MUST use HTTPS with TLS 1.3+ (TLS 1.2 minimum)
- Validate SSL certificates (no self-signed certificates in production)
- Implement HTTP Strict Transport Security (HSTS) headers
- Use secure cipher suites only (disable TLS 1.0/1.1)
Webhook Security:
// Example: HMAC signature verification for async callbacks
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const hmac = crypto.createHmac('sha256', secret);
const expectedSignature = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Webhook Best Practices:
- Always verify HMAC signatures on webhook payloads
- Use HTTPS for all webhook endpoints
- Implement webhook authentication (bearer tokens or mTLS)
- Use short-lived, cryptographically random task IDs and context IDs
- Consider webhook payload encryption for sensitive data
Bearer Token Risks
While bearer tokens (including JWT) are widely used across the industry, they carry specific risks that implementations should understand, particularly for high-risk financial operations:
Token Interception Risks:
- Domain Hijacking: If a domain expires or is compromised, attackers could receive valid bearer tokens intended for legitimate endpoints. Unlike OAuth flows, there’s no bilateral verification that the server is the intended recipient.
- Man-in-the-Middle: Without certificate pinning, compromised certificate authorities could enable token interception even over HTTPS.
- Token Replay: Stolen tokens can be replayed until expiration.
Mitigations for Bearer Token Usage:
| Risk | Mitigation |
|---|
| Domain hijacking | Certificate pinning, domain monitoring, short token expiry |
| Token interception | mTLS, OAuth 2.0 with PKCE, token binding |
| Token replay | Short expiration (15 min), one-time-use tokens, request signing |
| Credential theft | Hardware security modules, secure enclaves |
When to Use Stronger Authentication:
For high-risk operations (create_media_buy, update_media_buy), consider:
- OAuth 2.0 with PKCE: Provides bilateral verification through the authorization flow
- Mutual TLS (mTLS): Cryptographically verifies both client and server identity
- Request Signing: HMAC or asymmetric signatures on request payloads
Example: Request Signing for Financial Operations:
// Additional security layer for high-value transactions
interface SignedRequest {
payload: string;
timestamp: number;
signature: string; // HMAC-SHA256 of payload + timestamp
}
function signRequest(payload: object, secret: string): SignedRequest {
const payloadStr = JSON.stringify(payload);
const timestamp = Date.now();
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payloadStr + timestamp.toString());
return {
payload: payloadStr,
timestamp,
signature: hmac.digest('hex')
};
}
Financial Transaction Safety
AdCP’s create_media_buy and update_media_buy tasks commit real advertising budgets. Implementations MUST implement controls to prevent financial loss from bugs, attacks, or operational errors.
Idempotency
Critical Requirement: All state-changing operations MUST support idempotency to prevent duplicate charges.
// Example: Idempotent media buy creation
interface CreateMediaBuyRequest {
idempotency_key: string; // Client-provided, unique per operation
budget: {
amount: number;
currency: string;
};
// ... other parameters
}
class MediaBuyService {
async createMediaBuy(request: CreateMediaBuyRequest): Promise<MediaBuy> {
// Check if this idempotency key was already processed
const existing = await this.findByIdempotencyKey(request.idempotency_key);
if (existing) {
// Return existing result, don't charge again
return existing;
}
// Process new request
// Store idempotency key with result
// Return new media buy
}
}
Implementation Guidelines:
- Accept client-provided idempotency keys (UUIDs recommended)
- Store idempotency keys with operation results for minimum 24 hours
- Return identical response for duplicate requests within retention window
- Use atomic database transactions to prevent race conditions
Budget Validation
Pre-Flight Checks:
interface BudgetValidation {
requestedBudget: Money;
accountBalance: Money;
existingCommitments: Money;
dailySpendLimit: Money;
monthlySpendLimit: Money;
}
async function validateBudget(validation: BudgetValidation): Promise<void> {
const availableBalance = validation.accountBalance - validation.existingCommitments;
if (validation.requestedBudget > availableBalance) {
throw new InsufficientFundsError("Budget exceeds available balance");
}
if (validation.requestedBudget > validation.dailySpendLimit) {
throw new BudgetLimitError("Exceeds daily spend limit");
}
// Additional checks...
}
Required Controls:
- Validate budget values are positive, non-zero amounts
- Check currency codes are valid ISO 4217 codes
- Enforce account-level spending limits
- Prevent budget increases beyond configured thresholds without additional approval
- Validate budget is within product minimum/maximum if specified
Approval Workflows
High-Value Transaction Approval:
interface ApprovalPolicy {
requiresApprovalThreshold: Money;
approvers: string[]; // User IDs or roles
timeoutDuration: number; // milliseconds
}
async function requiresApproval(
budget: Money,
policy: ApprovalPolicy
): Promise<boolean> {
return budget.amount >= policy.requiresApprovalThreshold.amount;
}
Implementation Patterns:
- Define approval thresholds per principal or account
- Support multi-step approval for high-value campaigns
- Implement approval timeouts (auto-reject after N hours)
- Provide audit trail of approval decisions
- Support emergency override procedures with enhanced logging
Transaction Integrity
Atomic Operations:
// Example: Atomic budget commitment
async function commitBudget(mediaBuy: MediaBuy): Promise<void> {
await db.transaction(async (tx) => {
// 1. Check account balance
const account = await tx.accounts.findOne({ id: mediaBuy.principalId }, { lock: true });
// 2. Validate sufficient funds
if (account.balance < mediaBuy.budget.amount) {
throw new InsufficientFundsError();
}
// 3. Create media buy record
await tx.mediaBuys.insert(mediaBuy);
// 4. Deduct from account balance
await tx.accounts.update(
{ id: mediaBuy.principalId },
{ balance: account.balance - mediaBuy.budget.amount }
);
// 5. Create audit log entry
await tx.auditLog.insert({
type: 'budget_commitment',
principalId: mediaBuy.principalId,
amount: mediaBuy.budget.amount,
timestamp: new Date()
});
});
}
Best Practices:
- Use database transactions for multi-step financial operations
- Implement retry logic with exponential backoff for transient failures
- Log all financial operations immutably (append-only audit log)
- Implement reconciliation processes to detect discrepancies
- Support refunds and budget adjustments with full audit trail
Fraud Prevention
Anomaly Detection:
interface FraudDetectionRules {
maxDailyBudget: Money;
maxCampaignsPerDay: number;
unusualSpendingMultiplier: number; // e.g., 5x average
geoBlacklist: string[]; // Country codes
}
async function detectFraud(
principal: Principal,
request: CreateMediaBuyRequest,
rules: FraudDetectionRules
): Promise<FraudAlert[]> {
const alerts: FraudAlert[] = [];
// Check for unusual spending patterns
const recentSpend = await getRecentSpend(principal.id, 7); // 7 days
const avgDailySpend = recentSpend / 7;
if (request.budget.amount > avgDailySpend * rules.unusualSpendingMultiplier) {
alerts.push({
severity: 'high',
reason: 'Unusual spending spike detected',
recommendation: 'Require additional verification'
});
}
// Check campaign creation velocity
const todaysCampaigns = await getTodaysCampaignCount(principal.id);
if (todaysCampaigns >= rules.maxCampaignsPerDay) {
alerts.push({
severity: 'medium',
reason: 'High campaign creation velocity',
recommendation: 'Rate limit or require approval'
});
}
return alerts;
}
Fraud Prevention Measures:
- Monitor for suspicious spending patterns (sudden spikes, unusual geos)
- Implement velocity checks (campaigns per day, budget increase rate)
- Maintain blocklists for compromised credentials
- Use IP reputation services to detect bot traffic
- Implement device fingerprinting for high-risk operations
- Support fraud analyst review queues for flagged transactions
Reconciliation & Audit
Daily Reconciliation:
interface ReconciliationReport {
date: Date;
expectedCommitments: Money;
actualCommitments: Money;
discrepancies: Discrepancy[];
reconciliationStatus: 'clean' | 'discrepancies_found';
}
async function dailyReconciliation(): Promise<ReconciliationReport> {
// Compare sum of all media buy budgets vs. sum of budget deductions
// Identify any mismatches
// Generate report for review
// Alert on discrepancies above threshold
}
Audit Requirements:
- Log all budget commitments, modifications, and refunds
- Maintain immutable audit trail (append-only, tamper-evident)
- Implement daily reconciliation processes
- Support audit export for compliance purposes
- Retain financial audit logs for minimum 7 years (or per regulatory requirements)
Data Protection
Creative Assets
Access Control:
- Implement strict access controls on creative storage
- Pre-launch campaigns may contain confidential or competitive information
- Use signed URLs with expiration for creative preview links
- Prevent unauthorized access through URL guessing (use UUIDs or signed tokens)
Content Security:
// Example: Validate uploaded creative assets
async function validateCreative(file: UploadedFile): Promise<void> {
// Check file type
const allowedTypes = ['image/jpeg', 'image/png', 'video/mp4', 'text/html'];
if (!allowedTypes.includes(file.mimeType)) {
throw new InvalidFileTypeError();
}
// Check file size
const maxSize = 100 * 1024 * 1024; // 100MB
if (file.size > maxSize) {
throw new FileTooLargeError();
}
// Scan for malware
await malwareScanner.scan(file);
// For HTML creatives, sanitize to prevent XSS
if (file.mimeType === 'text/html') {
file.content = sanitizeHtml(file.content);
}
}
Security Measures:
- Validate file types and sizes
- Scan uploaded files for malware
- Sanitize HTML creatives to prevent XSS attacks
- Use content delivery networks (CDNs) with DDoS protection
- Implement rate limiting on creative uploads
- Watermark or fingerprint creatives to detect leaks
First-Party Signals
PII Protection:
- First-party signals MAY contain personally identifiable information
- Implementations MUST comply with GDPR, CCPA, and other privacy regulations
- Signal descriptions should use aggregate, non-identifying language
- Implement data minimization (collect only necessary signals)
Data Handling Requirements:
interface SignalPrivacyControls {
dataRetentionDays: number;
requiresUserConsent: boolean;
piiDetectionEnabled: boolean;
anonymizationRequired: boolean;
}
async function processSignal(
signal: Signal,
controls: SignalPrivacyControls
): Promise<void> {
// Detect PII in signal metadata
if (controls.piiDetectionEnabled) {
const containsPII = await detectPII(signal.description);
if (containsPII) {
throw new PIIDetectedError("Signal metadata contains PII");
}
}
// Check user consent
if (controls.requiresUserConsent) {
const hasConsent = await verifyConsent(signal.audienceId);
if (!hasConsent) {
throw new ConsentError("User consent required for signal usage");
}
}
// Set expiration based on retention policy
signal.expiresAt = addDays(new Date(), controls.dataRetentionDays);
}
Privacy Best Practices:
- Encrypt signals at rest and in transit
- Implement data retention policies (auto-delete after N days)
- Support data subject access requests (GDPR Article 15)
- Support right to deletion (GDPR Article 17)
- Provide transparency about data usage in privacy policies
- Obtain proper user consent before collecting/using signals
Targeting Briefs
Competitive Intelligence Protection:
- Targeting briefs may reveal competitive strategy
- Implement appropriate access controls (principal isolation)
- Log all access to targeting briefs for audit purposes
- Consider brief anonymization for compliance review workflows
- Prevent brief content from appearing in error messages or logs
Data Security:
- Encrypt briefs at rest
- Implement access logging and monitoring
- Support time-limited access (briefs expire after campaign completion)
- Provide secure deletion procedures
Multi-Party Trust Model
AdCP’s three-role architecture (Principal, Publisher, Orchestrator) introduces unique security considerations beyond typical client-server APIs.
Principal-Publisher Trust
Publisher Identity Verification:
interface PublisherVerification {
publisherId: string;
domainOwnership: boolean; // Verified via DNS TXT record or .well-known
businessVerification: boolean; // D-U-N-S, business license, etc.
reputationScore: number;
lastAuditDate: Date;
}
async function verifyPublisher(publisherId: string): Promise<PublisherVerification> {
// Check publisher registry or reputation system
// Verify domain ownership
// Check for fraud reports or complaints
// Return verification status
}
Trust Establishment:
- Principals should verify publisher identity before committing budgets
- Use trusted publisher registries (e.g., IAB Tech Lab Ads.txt/Sellers.json)
- Implement publisher reputation scoring
- Support allowlists/blocklists at principal level
- Log all publisher interactions for dispute resolution
Budget Protection:
- Start with small test campaigns before large commitments
- Monitor delivery quality and fraud indicators
- Implement automated pause if fraud detected
- Support refund/chargeback mechanisms for non-delivery
Orchestrator Security
Orchestrators manage credentials and operations for multiple principals, requiring enhanced security controls.
Credential Isolation:
class SecureOrchestrator {
private credentialVault: KeyManagementService;
async executeForPrincipal(
principalId: string,
task: AdCPTask
): Promise<TaskResult> {
// Retrieve principal-specific credentials (encrypted at rest)
const credentials = await this.credentialVault.getCredentials(principalId);
// Execute task with principal's credentials, not orchestrator's
const result = await adcpClient.execute(task, credentials);
// Never log credentials or sensitive details
this.auditLog.record({
principalId,
taskType: task.type,
timestamp: new Date(),
success: result.success
// NO credentials, NO PII, NO competitive intel
});
return result;
}
}
Required Controls:
- Store each principal’s credentials separately (encrypted at rest)
- Use least-privilege credentials (scoped to necessary operations only)
- Implement strict data isolation (Principal A cannot access Principal B’s data)
- Log all operations with principal identity
- Support per-principal rate limiting
- Implement audit trails for compliance
Multi-Tenancy Security:
- Use row-level security in databases
- Implement principal_id filtering in all queries
- Prevent cross-principal data leakage in error messages
- Separate compute resources per principal (or use sandboxing)
- Monitor for privilege escalation attempts
Data Isolation Requirements
Campaign Data Isolation:
// Example: Query with mandatory principal isolation
async function getMediaBuy(
mediaBuyId: string,
principalId: string // Always required, never optional
): Promise<MediaBuy> {
// ALWAYS filter by principal_id - never query without it
const mediaBuy = await db.mediaBuys.findOne({
id: mediaBuyId,
principal_id: principalId // ← Critical: prevents cross-principal access
});
if (!mediaBuy) {
// Generic error - don't reveal if campaign exists for another principal
throw new NotFoundError("Media buy not found");
}
return mediaBuy;
}
Isolation Boundaries:
- Media buy records MUST be scoped to principal
- Creative assets MUST be scoped to principal
- Targeting briefs MUST be scoped to principal
- Delivery reports MUST be scoped to principal
- Signals MUST be scoped to appropriate audience (never cross-principal)
Testing Isolation:
// Security test: Ensure cross-principal access is blocked
describe('Principal Isolation', () => {
it('prevents Principal A from accessing Principal B data', async () => {
const principalA = 'principal_a';
const principalB = 'principal_b';
// Create media buy for Principal B
const mediaBuy = await createMediaBuy({ principalId: principalB });
// Attempt to access with Principal A credentials
await expect(
getMediaBuy(mediaBuy.id, principalA)
).rejects.toThrow(NotFoundError);
// Generic error - no information leakage
});
});
## Input Validation & API Security
### Request Validation
All user-provided input MUST be validated before processing to prevent injection attacks, resource exhaustion, and data corruption.
<Info>
**Using official client libraries?** The [JavaScript SDK](https://www.npmjs.com/package/@adcp/client) and [Python SDK](https://pypi.org/project/adcp/) include built-in schema validation. The example below is for custom implementations in languages without official SDK support.
</Info>
**Type Validation** (Go example for custom implementations):
```go
package adcp
import (
"encoding/json"
"errors"
"regexp"
"time"
)
// CreateMediaBuyRequest models the validated request
type CreateMediaBuyRequest struct {
ProductID string `json:"product_id"`
TargetingBrief string `json:"targeting_brief"`
Budget Budget `json:"budget"`
FlightDates FlightDates `json:"flight_dates"`
CreativeID *string `json:"creative_id,omitempty"`
}
type Budget struct {
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
type FlightDates struct {
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
}
var uuidRegex = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
var allowedCurrencies = map[string]bool{"USD": true, "EUR": true, "GBP": true, "JPY": true}
// Validate performs schema and business logic validation per AdCP spec
func (req *CreateMediaBuyRequest) Validate() error {
// Product ID must be valid UUID
if !uuidRegex.MatchString(req.ProductID) {
return errors.New("product_id must be valid UUID")
}
// Targeting brief length constraints
if len(req.TargetingBrief) < 10 || len(req.TargetingBrief) > 5000 {
return errors.New("targeting_brief must be 10-5000 characters")
}
// Budget validation - positive and under limit
if req.Budget.Amount <= 0 || req.Budget.Amount > 10000000 {
return errors.New("budget amount must be positive and under $10M")
}
// Currency must be in allowed list (not user input)
if !allowedCurrencies[req.Budget.Currency] {
return errors.New("invalid currency code")
}
// Flight dates validation
if !req.FlightDates.EndDate.After(req.FlightDates.StartDate) {
return errors.New("end_date must be after start_date")
}
return nil
}
// ParseAndValidate demonstrates full validation workflow
func ParseAndValidate(jsonData []byte) (*CreateMediaBuyRequest, error) {
var req CreateMediaBuyRequest
// Step 1: Deserialize with strict parsing (reject unknown fields)
decoder := json.NewDecoder(bytes.NewReader(jsonData))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&req); err != nil {
return nil, fmt.Errorf("invalid JSON structure: %w", err)
}
// Step 2: Business logic validation
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
return &req, nil
}
Injection Prevention:
- Use parameterized queries for all database operations (NEVER string concatenation)
- Sanitize HTML creatives to prevent XSS
- Validate URLs in webhook configurations
- Use allow-lists for enum values (product IDs, currencies, format IDs)
- Reject requests with unexpected fields (strict schema validation)
Resource Limits:
const INPUT_LIMITS = {
targeting_brief_max_length: 5000,
creative_upload_max_size: 100 * 1024 * 1024, // 100MB
max_formats_per_request: 50,
max_products_per_query: 100,
max_date_range_days: 365
};
API Security Best Practices
CORS Configuration:
// Only for browser-based agents (not server-to-server)
app.use(cors({
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
maxAge: 86400 // 24 hours
}));
Security Headers:
app.use((req, res, next) => {
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'DENY');
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// Enable browser XSS protection
res.setHeader('X-XSS-Protection', '1; mode=block');
// HSTS for HTTPS enforcement
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
// Content Security Policy
res.setHeader('Content-Security-Policy', "default-src 'self'");
next();
});
Error Handling:
// NEVER expose internal details in error responses
app.use((err, req, res, next) => {
// Log full error internally
logger.error('Request failed', {
error: err.message,
stack: err.stack,
requestId: req.id,
principalId: req.auth?.principalId
});
// Return generic error to client (no stack traces, no internal paths)
res.status(err.statusCode || 500).json({
error: {
code: err.code || 'internal_error',
message: err.message || 'An internal error occurred',
// NO: stack traces, file paths, database errors, internal IDs
}
});
});
Rate Limiting & DDoS Protection
Rate Limiting Strategy
Implement multi-tiered rate limiting to prevent abuse while allowing legitimate usage.
Per-Endpoint Limits:
const RATE_LIMITS = {
// Discovery tasks (read-only, cacheable)
'get_products': { requests: 100, window: '1m' },
'list_creative_formats': { requests: 100, window: '1m' },
'list_authorized_properties': { requests: 100, window: '1m' },
// Campaign management (state-changing, expensive)
'create_media_buy': { requests: 10, window: '1m' },
'update_media_buy': { requests: 20, window: '1m' },
// Creative operations (file uploads, processing)
'sync_creatives': { requests: 50, window: '1m' },
'build_creative': { requests: 5, window: '1m' }, // Generative, expensive
'preview_creative': { requests: 30, window: '1m' },
// Reporting (database-intensive)
'get_media_buy_delivery': { requests: 200, window: '1m' },
'provide_performance_feedback': { requests: 100, window: '1m' },
// Signals (if implemented)
'get_signals': { requests: 100, window: '1m' },
'activate_signal': { requests: 10, window: '1m' }
};
Implementation:
import rateLimit from 'express-rate-limit';
// Per-IP rate limiting (basic DDoS protection)
const ipLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 500, // 500 requests per minute per IP
message: 'Too many requests from this IP, please try again later'
});
// Per-credential rate limiting (per principal)
const credentialLimiter = (maxRequests: number) => rateLimit({
windowMs: 60 * 1000,
max: maxRequests,
keyGenerator: (req) => req.auth.principalId, // Rate limit by principal
handler: (req, res) => {
res.status(429).json({
error: {
code: 'rate_limit_exceeded',
message: `Rate limit exceeded. Max ${maxRequests} requests per minute.`,
retry_after: Math.ceil(req.rateLimit.resetTime / 1000)
}
});
}
});
// Apply to specific endpoints
app.post('/create_media_buy',
ipLimiter,
credentialLimiter(RATE_LIMITS.create_media_buy.requests),
createMediaBuyHandler
);
Adaptive Rate Limiting:
// Adjust limits based on account tier or reputation
function getRateLimitForPrincipal(principalId: string): number {
const principal = getPrincipal(principalId);
const baseLimit = 10;
// Premium accounts get higher limits
if (principal.tier === 'enterprise') {
return baseLimit * 5;
}
// New accounts get lower limits until reputation established
const accountAgeDays = daysSince(principal.createdAt);
if (accountAgeDays < 7) {
return baseLimit * 0.5;
}
// Accounts with good reputation get higher limits
if (principal.reputationScore > 0.9) {
return baseLimit * 2;
}
return baseLimit;
}
Circuit Breaker Pattern:
// Protect downstream services from overload
class CircuitBreaker {
private failures = 0;
private lastFailureTime = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
// Check if we should try again
if (Date.now() - this.lastFailureTime > 60000) {
this.state = 'half-open';
} else {
throw new ServiceUnavailableError('Circuit breaker is open');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = 'closed';
}
private onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= 5) {
this.state = 'open';
}
}
}
Logging, Monitoring & Incident Response
Security Logging
Required Log Events:
interface SecurityLog {
timestamp: Date;
eventType: 'auth' | 'authorization' | 'financial' | 'data_access' | 'error';
severity: 'info' | 'warning' | 'error' | 'critical';
principalId?: string;
ipAddress: string;
userAgent: string;
action: string;
resource?: string;
outcome: 'success' | 'failure';
errorCode?: string;
requestId: string;
// NEVER: credentials, PII, targeting briefs, internal IDs
}
// Examples
logger.info({
eventType: 'auth',
action: 'login_success',
principalId: 'principal_123',
ipAddress: '203.0.113.45',
outcome: 'success'
});
logger.warning({
eventType: 'financial',
action: 'budget_limit_approached',
principalId: 'principal_123',
resource: 'media_buy_456',
outcome: 'warning',
details: { currentSpend: 9500, limit: 10000 }
});
logger.error({
eventType: 'authorization',
action: 'cross_principal_access_attempt',
principalId: 'principal_123',
resource: 'media_buy_789', // Belongs to principal_456
outcome: 'failure',
errorCode: 'unauthorized'
});
Log Categories:
- Authentication Events: Login attempts, token validation, logout
- Authorization Events: Permission checks, access denials, privilege escalation attempts
- Financial Events: Budget commitments, spending thresholds, fraud alerts
- Data Access Events: Creative downloads, signal access, report generation
- Error Events: Exceptions, validation failures, system errors
Log Protection:
// Sanitize sensitive data before logging
function sanitizeForLogging(obj: any): any {
const sanitized = { ...obj };
// Remove sensitive fields
const sensitiveFields = [
'password', 'api_key', 'token', 'secret',
'credit_card', 'ssn', 'email', 'phone',
'targeting_brief' // May contain competitive intelligence
];
for (const field of sensitiveFields) {
if (field in sanitized) {
sanitized[field] = '[REDACTED]';
}
}
return sanitized;
}
Log Retention:
- Security logs: 90 days minimum (365 days recommended)
- Financial logs: 7 years (compliance requirement)
- Access logs: 30 days minimum
- Error logs: 90 days
Monitoring & Alerting
Critical Alerts (immediate response required):
const CRITICAL_ALERTS = {
// Authentication
'multiple_failed_logins': {
threshold: 10,
window: '5m',
action: 'lock_account'
},
// Financial
'unusual_spending_spike': {
threshold: '5x average',
window: '1h',
action: 'require_approval'
},
// Security
'cross_principal_access_attempt': {
threshold: 1,
window: '1m',
action: 'alert_security_team'
},
// Availability
'high_error_rate': {
threshold: '5%',
window: '5m',
action: 'page_oncall'
}
};
Security Metrics to Monitor:
// Real-time dashboards should track:
const SECURITY_METRICS = {
// Authentication
'failed_auth_rate': 'percentage of failed auth attempts',
'new_principals_per_day': 'account creation velocity',
'locked_accounts': 'accounts locked due to suspicious activity',
// Authorization
'authorization_failures': 'permission denial count',
'cross_principal_attempts': 'isolation violation attempts',
// Financial
'total_budget_committed': 'sum of active campaign budgets',
'fraud_alerts': 'suspicious transaction count',
'high_value_transactions': 'transactions above threshold',
// Operational
'api_error_rate': 'percentage of failed requests',
'rate_limit_hits': 'requests blocked by rate limiting',
'circuit_breaker_trips': 'downstream service failures',
// Data Access
'creative_downloads': 'creative asset access count',
'signal_activations': 'audience signal usage',
'export_requests': 'data export events'
};
Anomaly Detection:
// Example: Detect unusual geographic access patterns
async function detectGeoAnomaly(principalId: string, ipAddress: string): Promise<boolean> {
const location = await geolocate(ipAddress);
const recentLocations = await getRecentLocations(principalId, 30); // Last 30 days
// Alert if access from new country
if (!recentLocations.includes(location.country)) {
logger.warning({
eventType: 'auth',
action: 'access_from_new_country',
principalId,
details: { country: location.country }
});
// Require additional verification
return true;
}
return false;
}
Incident Response
Incident Response Plan:
1. Detection
- Automated monitoring and alerting
- Manual reports from users or partners
- External security researcher reports
2. Assessment (within 1 hour)
- Determine scope and severity
- Identify affected principals and campaigns
- Estimate potential financial impact
- Classify incident (data breach, fraud, availability, etc.)
3. Containment (immediate)
// Emergency response actions
async function containIncident(incident: SecurityIncident): Promise<void> {
switch (incident.type) {
case 'compromised_credentials':
// Immediately revoke compromised credentials
await revokeCredentials(incident.principalId);
// Require password reset
await requirePasswordReset(incident.principalId);
// Review recent activity for fraud
await auditRecentActivity(incident.principalId);
break;
case 'budget_fraud':
// Pause all campaigns for affected principal
await pauseAllCampaigns(incident.principalId);
// Prevent new budget commitments
await lockAccount(incident.principalId);
// Initiate refund process if applicable
await initiateRefund(incident.affectedTransactions);
break;
case 'data_breach':
// Identify exposed data
await identifyExposedData(incident);
// Notify affected parties
await notifyAffectedPrincipals(incident);
// Secure vulnerable systems
await patchVulnerability(incident.vulnerability);
break;
}
}
4. Communication
- Internal: Notify security team, engineering, management
- External: Notify affected principals within 24 hours
- Regulatory: File required breach notifications (GDPR 72 hours, CCPA 30 days)
- Public: Issue security advisory if widely impacted
5. Recovery
- Restore affected systems and data
- Refund fraudulent transactions
- Rebuild customer trust
- Document incident timeline
6. Post-Incident Review
- Root cause analysis
- Update security controls
- Improve monitoring and detection
- Train team on lessons learned
- Update incident response procedures
Communication Templates:
// Principal notification for compromised account
const INCIDENT_NOTIFICATION = {
subject: 'Security Alert: Account Activity Detected',
body: `
We detected unusual activity on your AdCP account and have taken
precautionary measures to protect your budget and campaigns.
What happened:
- Multiple failed login attempts from unfamiliar location
- Your credentials may have been compromised
What we've done:
- Temporarily locked your account
- Paused all active campaigns
- Reviewed recent transactions for fraud
What you should do:
1. Reset your password immediately
2. Review recent campaign activity for unauthorized changes
3. Update any shared credentials
4. Enable two-factor authentication
If you have questions or did not recognize this activity, please
contact our security team immediately at security@adcontextprotocol.org
Reference ID: ${incidentId}
`
};
Compliance & Regulatory Considerations
AdCP implementations may need to comply with various regulations depending on jurisdiction, data handling practices, and business relationships.
Privacy Regulations
GDPR (General Data Protection Regulation):
- Scope: Applies to processing personal data of EU residents
- AdCP Context: First-party signals may constitute personal data
- Requirements:
- Lawful basis for processing (consent, legitimate interest, etc.)
- Data subject rights (access, deletion, portability, rectification)
- Data protection impact assessments for high-risk processing
- Data breach notification (72 hours to supervisory authority)
- Privacy by design and default
- Documentation: Maintain records of processing activities
CCPA (California Consumer Privacy Act):
- Scope: Applies to California residents’ personal information
- AdCP Context: Consumer targeting and audience data
- Requirements:
- Consumer rights (know, delete, opt-out, non-discrimination)
- Notice at collection
- Privacy policy disclosures
- Service provider agreements
- Data breach notification (if unencrypted data exposed)
- Special Considerations: “Sale” of personal information requires opt-out
Other Regulations:
- COPPA: Children’s Online Privacy Protection Act (US) - special consent requirements for under-13 audience
- PIPEDA: Personal Information Protection and Electronic Documents Act (Canada)
- Data Localization: Some jurisdictions require data to be stored within borders (Russia, China, etc.)
Industry Self-Regulation
IAB Tech Lab Standards:
- Ads.txt/App-ads.txt: Publisher authorization verification
- Sellers.json: Supply chain transparency
- TCF (Transparency & Consent Framework): GDPR consent management
Digital Advertising Alliance (DAA):
- Self-regulatory principles for interest-based advertising
- Consumer choice mechanisms (AdChoices icon)
- Enhanced notice requirements
Network Advertising Initiative (NAI):
- Code of Conduct for member companies
- Opt-out mechanisms for interest-based advertising
Financial & Audit Compliance
SOC 2 (Service Organization Control 2):
- Recommended for: Publishers and Orchestrators handling financial transactions
- Trust Service Criteria: Security, availability, processing integrity, confidentiality, privacy
- Type II: Audits effectiveness of controls over time (6-12 months)
PCI DSS (Payment Card Industry Data Security Standard):
- Less common in programmatic advertising (indirect billing more typical)
- Required if: Directly processing, storing, or transmitting cardholder data
- Levels: Based on transaction volume
Financial Audit Requirements:
- Maintain auditable financial records for minimum 7 years
- Support reconciliation and dispute resolution
- Provide transaction-level audit trails
Security Assessment & Testing
Recommended Security Assessments:
| Assessment Type | Frequency | Purpose |
|---|
| Vulnerability Scanning | Weekly | Identify known vulnerabilities in dependencies and infrastructure |
| Penetration Testing | Annually | Simulate real-world attacks to identify exploitable weaknesses |
| Code Security Review | Per major release | Identify security flaws in custom code |
| Third-Party Security Audit | Annually | Independent validation of security controls |
| Compliance Audit (SOC 2) | Annually | Verify compliance with security frameworks |
Bug Bounty Programs:
Consider running a bug bounty program to incentivize responsible vulnerability disclosure:
- Define scope (in-scope vs. out-of-scope systems)
- Set bounty amounts based on severity
- Establish clear disclosure guidelines
- Respond promptly to submissions
Security Implementation Checklist
For Publishers (AdCP Servers)
Authentication & Authorization:
Financial Controls:
Data Protection:
Operational Security:
Monitoring & Incident Response:
Compliance:
Testing & Assessment:
For Principals (AdCP Clients)
Credential Security:
Budget Protection:
Data Security:
Operational:
For Orchestrators (Multi-Principal Agents)
Credential Management:
Data Isolation:
Operational Security:
Compliance:
Vulnerability Disclosure
For vulnerability disclosure policy and reporting procedures, see SECURITY.md in the AdCP repository.
Quick Reference:
- Report vulnerabilities to: security@adcontextprotocol.org
- Expected response time: Within 72 hours
- Public disclosure timeline: After fix is available (coordinated disclosure)
Security Resources
Standards & Frameworks:
Industry Resources:
Compliance Guides:
Tools & Libraries: