@the_ro_show/agent-ads-sdk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +169 -0
- package/SECURITY.md +331 -5
- package/dist/index.d.mts +177 -1
- package/dist/index.d.ts +177 -1
- package/dist/index.js +399 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +395 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -55,6 +55,137 @@ if (unit && unit.unit_type === 'sponsored_suggestion') {
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
## Security Best Practices
|
|
59
|
+
|
|
60
|
+
🔴 **CRITICAL SECURITY REQUIREMENTS**
|
|
61
|
+
|
|
62
|
+
### 1. Server-Side Only
|
|
63
|
+
|
|
64
|
+
**This SDK MUST only be used server-side.** Your API key provides full access to your account and billing.
|
|
65
|
+
|
|
66
|
+
✅ **Safe:** Node.js, serverless functions, server-side rendering
|
|
67
|
+
❌ **Unsafe:** Browser JavaScript, mobile apps without backend proxy
|
|
68
|
+
|
|
69
|
+
### 2. Sanitize Ad Content Before Display
|
|
70
|
+
|
|
71
|
+
Ad content can contain malicious HTML/JavaScript. **Always sanitize** before rendering in HTML:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { escapeHTML, sanitizeURL } from '@the_ro_show/agent-ads-sdk';
|
|
75
|
+
|
|
76
|
+
// ✅ SAFE: Sanitize content
|
|
77
|
+
const safeTitle = escapeHTML(unit.suggestion.title);
|
|
78
|
+
const safeBody = escapeHTML(unit.suggestion.body);
|
|
79
|
+
const safeURL = sanitizeURL(unit.suggestion.action_url);
|
|
80
|
+
|
|
81
|
+
if (safeURL) {
|
|
82
|
+
element.innerHTML = `<a href="${safeURL}">${safeTitle}</a>`;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// ❌ DANGEROUS: Direct HTML injection (XSS vulnerability!)
|
|
88
|
+
element.innerHTML = unit.suggestion.title;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. Validate URLs
|
|
92
|
+
|
|
93
|
+
Always validate `action_url` before using:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const safeURL = sanitizeURL(unit.suggestion.action_url);
|
|
97
|
+
|
|
98
|
+
if (!safeURL) {
|
|
99
|
+
console.error('Dangerous URL blocked');
|
|
100
|
+
return; // Don't render the ad
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Blocks dangerous protocols: `javascript:`, `data:`, `file:`
|
|
105
|
+
|
|
106
|
+
### 4. See Complete Guidelines
|
|
107
|
+
|
|
108
|
+
📖 **[Read SECURITY.md](./SECURITY.md)** for comprehensive security guidelines including:
|
|
109
|
+
- XSS prevention examples
|
|
110
|
+
- Phishing protection
|
|
111
|
+
- Rate limiting
|
|
112
|
+
- Input validation
|
|
113
|
+
- Security checklist
|
|
114
|
+
|
|
115
|
+
## Testing Without Advertiser Data
|
|
116
|
+
|
|
117
|
+
Use `MockAttentionMarketClient` to test your integration without needing real ad campaigns.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { MockAttentionMarketClient, createOpportunity, generateUUID } from '@the_ro_show/agent-ads-sdk';
|
|
121
|
+
|
|
122
|
+
// Use mock client during development
|
|
123
|
+
const client = new MockAttentionMarketClient({
|
|
124
|
+
latencyMs: 100, // Simulate API latency
|
|
125
|
+
fillRate: 1.0, // 100% fill rate for testing
|
|
126
|
+
verbose: true, // Log mock activity
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const opportunity = createOpportunity({
|
|
130
|
+
taxonomy: 'local_services.movers.quote',
|
|
131
|
+
country: 'US',
|
|
132
|
+
language: 'en',
|
|
133
|
+
platform: 'web',
|
|
134
|
+
query: 'Find movers in Brooklyn',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const unit = await client.decide({
|
|
138
|
+
request_id: generateUUID(),
|
|
139
|
+
agent_id: 'agt_test',
|
|
140
|
+
placement: { type: 'sponsored_suggestion', surface: 'chat_response' },
|
|
141
|
+
opportunity,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Returns realistic mock ad data immediately
|
|
145
|
+
if (unit && unit.unit_type === 'sponsored_suggestion') {
|
|
146
|
+
console.log(unit.suggestion.title); // "Professional Moving Services - Same Day Available"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Available mock taxonomies:**
|
|
151
|
+
- `local_services.movers.quote`
|
|
152
|
+
- `local_services.restaurants.search`
|
|
153
|
+
- `local_services.plumbers.quote`
|
|
154
|
+
- `local_services.electricians.quote`
|
|
155
|
+
- `local_services.cleaners.quote`
|
|
156
|
+
- `shopping.electronics.search`
|
|
157
|
+
|
|
158
|
+
**Add custom mock ads:**
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
client.addMockUnit('your.custom.taxonomy', {
|
|
162
|
+
unit_id: 'unit_custom_001',
|
|
163
|
+
unit_type: 'sponsored_suggestion',
|
|
164
|
+
disclosure: { label: 'Sponsored', sponsor_name: 'Your Sponsor' },
|
|
165
|
+
tracking: { token: 'trk_test', impression_url: '...', click_url: '...' },
|
|
166
|
+
suggestion: {
|
|
167
|
+
title: 'Your Ad Title',
|
|
168
|
+
body: 'Your ad body text',
|
|
169
|
+
cta: 'Call to Action',
|
|
170
|
+
action_url: 'https://example.com',
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Run the full test suite:**
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx tsx examples/test-with-mocks.ts
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Switch to production:**
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const client = process.env.NODE_ENV === 'production'
|
|
185
|
+
? new AttentionMarketClient({ apiKey: process.env.ATTENTIONMARKET_API_KEY })
|
|
186
|
+
: new MockAttentionMarketClient();
|
|
187
|
+
```
|
|
188
|
+
|
|
58
189
|
## Agent Integration Examples
|
|
59
190
|
|
|
60
191
|
### Minimal Examples (< 80 lines)
|
|
@@ -74,10 +205,12 @@ For production integrations:
|
|
|
74
205
|
- **[Claude (Anthropic)](./examples/claude-tool-use-full.ts)** - Complete tool use pattern with schemas and tracking
|
|
75
206
|
- **[OpenAI GPT](./examples/openai-function-calling-full.ts)** - Complete function calling with integration checklist
|
|
76
207
|
- **[Google Gemini](./examples/gemini-function-calling-full.ts)** - Complete function declarations with testing guide
|
|
208
|
+
- **[Safe Web Rendering](./examples/safe-web-rendering.ts)** - XSS prevention and secure HTML rendering
|
|
77
209
|
|
|
78
210
|
Run any example with:
|
|
79
211
|
```bash
|
|
80
212
|
npx tsx examples/claude-tool-use-minimal.ts
|
|
213
|
+
npx tsx examples/safe-web-rendering.ts
|
|
81
214
|
```
|
|
82
215
|
|
|
83
216
|
## Full Example with Raw Response
|
|
@@ -334,6 +467,42 @@ import { generateUUID } from '@the_ro_show/agent-ads-sdk';
|
|
|
334
467
|
const requestId = generateUUID();
|
|
335
468
|
```
|
|
336
469
|
|
|
470
|
+
### `escapeHTML(text): string`
|
|
471
|
+
|
|
472
|
+
Escape HTML special characters to prevent XSS attacks.
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { escapeHTML } from '@the_ro_show/agent-ads-sdk';
|
|
476
|
+
|
|
477
|
+
const safeTitle = escapeHTML(unit.suggestion.title);
|
|
478
|
+
element.innerHTML = safeTitle; // Safe from XSS
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Escapes: `&`, `<`, `>`, `"`, `'`, `/`
|
|
482
|
+
|
|
483
|
+
### `sanitizeURL(url, options?): string | null`
|
|
484
|
+
|
|
485
|
+
Validate and sanitize URLs to prevent XSS and phishing attacks.
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { sanitizeURL } from '@the_ro_show/agent-ads-sdk';
|
|
489
|
+
|
|
490
|
+
const safeURL = sanitizeURL(unit.suggestion.action_url);
|
|
491
|
+
|
|
492
|
+
if (safeURL) {
|
|
493
|
+
window.open(safeURL, '_blank');
|
|
494
|
+
} else {
|
|
495
|
+
console.error('Dangerous URL blocked');
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Blocked protocols:** `javascript:`, `data:`, `file:`, `vbscript:`
|
|
500
|
+
|
|
501
|
+
**Options:**
|
|
502
|
+
- `allowHttp: boolean` - Allow HTTP URLs (default: false, HTTPS only)
|
|
503
|
+
- `allowTel: boolean` - Allow tel: links (default: true)
|
|
504
|
+
- `allowMailto: boolean` - Allow mailto: links (default: true)
|
|
505
|
+
|
|
337
506
|
## Features
|
|
338
507
|
|
|
339
508
|
- ✅ TypeScript support with full type definitions
|
package/SECURITY.md
CHANGED
|
@@ -1,10 +1,71 @@
|
|
|
1
1
|
# Security Policy
|
|
2
2
|
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Server-Side Only Usage](#server-side-only-usage)
|
|
6
|
+
2. [API Key Management](#api-key-management)
|
|
7
|
+
3. [Cross-Site Scripting (XSS) Prevention](#cross-site-scripting-xss-prevention)
|
|
8
|
+
4. [Content Security & Phishing](#content-security--phishing)
|
|
9
|
+
5. [Input Validation](#input-validation)
|
|
10
|
+
6. [Rate Limiting & Abuse Prevention](#rate-limiting--abuse-prevention)
|
|
11
|
+
7. [Reporting Security Issues](#reporting-security-issues)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Server-Side Only Usage
|
|
16
|
+
|
|
17
|
+
🔴 **CRITICAL: This SDK MUST only be used server-side.**
|
|
18
|
+
|
|
19
|
+
Your API key provides full access to your account and billing. **Never use this SDK in client-side code** (browser, mobile apps, etc.).
|
|
20
|
+
|
|
21
|
+
### ✅ Safe Environments
|
|
22
|
+
|
|
23
|
+
- Node.js backend servers
|
|
24
|
+
- Serverless functions (AWS Lambda, Vercel Functions, Cloudflare Workers)
|
|
25
|
+
- Server-side rendering (Next.js getServerSideProps, SvelteKit load)
|
|
26
|
+
|
|
27
|
+
### ❌ Unsafe Environments
|
|
28
|
+
|
|
29
|
+
- Browser/frontend JavaScript
|
|
30
|
+
- Mobile apps (React Native, Flutter without backend proxy)
|
|
31
|
+
- Electron apps (renderer process)
|
|
32
|
+
- Browser extensions
|
|
33
|
+
|
|
34
|
+
### Client-Side Apps
|
|
35
|
+
|
|
36
|
+
If you need to show ads in a client-side app:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// ✅ CORRECT: Backend API route
|
|
40
|
+
// /api/get-ad
|
|
41
|
+
export async function GET(request) {
|
|
42
|
+
const client = new AttentionMarketClient({
|
|
43
|
+
apiKey: process.env.ATTENTIONMARKET_API_KEY, // Server-side only
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const unit = await client.decide({...});
|
|
47
|
+
return Response.json(unit);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ✅ CORRECT: Frontend fetches from your backend
|
|
51
|
+
const response = await fetch('/api/get-ad');
|
|
52
|
+
const unit = await response.json();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ❌ WRONG: API key exposed in browser
|
|
57
|
+
const client = new AttentionMarketClient({
|
|
58
|
+
apiKey: 'am_live_...', // EXPOSED TO USERS!
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
3
64
|
## API Key Management
|
|
4
65
|
|
|
5
66
|
**IMPORTANT**: Never commit API keys to version control.
|
|
6
67
|
|
|
7
|
-
Your AttentionMarket API key (`am_live_...` or `am_test_...`) provides access to your agent's account and billing.
|
|
68
|
+
Your AttentionMarket API key (`am_live_...` or `am_test_...`) provides access to your agent's account and billing.
|
|
8
69
|
|
|
9
70
|
### Environment Variables
|
|
10
71
|
|
|
@@ -12,6 +73,7 @@ Store your API key in environment variables, not in code:
|
|
|
12
73
|
|
|
13
74
|
```bash
|
|
14
75
|
export ATTENTIONMARKET_API_KEY=am_live_...
|
|
76
|
+
export ATTENTIONMARKET_AGENT_ID=agt_01HV...
|
|
15
77
|
```
|
|
16
78
|
|
|
17
79
|
Then use it in your application:
|
|
@@ -30,21 +92,285 @@ Ensure your `.gitignore` includes:
|
|
|
30
92
|
.env
|
|
31
93
|
.env.local
|
|
32
94
|
.env.*.local
|
|
95
|
+
*.pem
|
|
96
|
+
*.key
|
|
97
|
+
credentials.json
|
|
33
98
|
```
|
|
34
99
|
|
|
35
100
|
### Production Deployments
|
|
36
101
|
|
|
37
|
-
- Use secure secret management
|
|
38
|
-
- Rotate API keys periodically
|
|
102
|
+
- Use secure secret management (AWS Secrets Manager, GitHub Secrets, HashiCorp Vault)
|
|
103
|
+
- Rotate API keys periodically (at least every 90 days)
|
|
39
104
|
- Use `am_test_...` keys for development and testing
|
|
40
105
|
- Use `am_live_...` keys only in production environments
|
|
106
|
+
- Never log API keys (even partially masked)
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Cross-Site Scripting (XSS) Prevention
|
|
111
|
+
|
|
112
|
+
🔴 **CRITICAL: Ad content can contain malicious HTML/JavaScript.**
|
|
113
|
+
|
|
114
|
+
Advertisers could inject malicious code into ad titles, bodies, or CTAs. You **MUST** sanitize all ad content before displaying it.
|
|
115
|
+
|
|
116
|
+
### The Risk
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// ❌ DANGEROUS: Direct HTML injection
|
|
120
|
+
const unit = await client.decide({...});
|
|
121
|
+
document.getElementById('ad').innerHTML = unit.suggestion.title;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If `title` contains `<img src=x onerror=alert(document.cookie)>`, this executes JavaScript and steals cookies.
|
|
125
|
+
|
|
126
|
+
### Safe Rendering
|
|
127
|
+
|
|
128
|
+
**Option 1: Use SDK sanitization helpers** (Recommended)
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { escapeHTML, sanitizeURL } from '@the_ro_show/agent-ads-sdk';
|
|
132
|
+
|
|
133
|
+
if (unit && unit.unit_type === 'sponsored_suggestion') {
|
|
134
|
+
const safeTitle = escapeHTML(unit.suggestion.title);
|
|
135
|
+
const safeBody = escapeHTML(unit.suggestion.body);
|
|
136
|
+
const safeCTA = escapeHTML(unit.suggestion.cta);
|
|
137
|
+
const safeURL = sanitizeURL(unit.suggestion.action_url);
|
|
138
|
+
|
|
139
|
+
// Safe to inject
|
|
140
|
+
document.getElementById('ad-title').innerHTML = safeTitle;
|
|
141
|
+
document.getElementById('ad-body').innerHTML = safeBody;
|
|
142
|
+
|
|
143
|
+
// For links, also validate URL scheme
|
|
144
|
+
if (safeURL) {
|
|
145
|
+
document.getElementById('ad-link').href = safeURL;
|
|
146
|
+
document.getElementById('ad-link').textContent = safeCTA;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Option 2: Use textContent instead of innerHTML**
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// ✅ SAFE: Text-only rendering
|
|
155
|
+
document.getElementById('ad-title').textContent = unit.suggestion.title;
|
|
156
|
+
document.getElementById('ad-body').textContent = unit.suggestion.body;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Option 3: Use a sanitization library**
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import DOMPurify from 'dompurify';
|
|
163
|
+
|
|
164
|
+
const safeHTML = DOMPurify.sanitize(unit.suggestion.title);
|
|
165
|
+
document.getElementById('ad').innerHTML = safeHTML;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### React/Vue/Angular
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// ✅ React (automatically escapes)
|
|
172
|
+
<div className="ad-title">{unit.suggestion.title}</div>
|
|
173
|
+
|
|
174
|
+
// ❌ React (dangerous)
|
|
175
|
+
<div dangerouslySetInnerHTML={{__html: unit.suggestion.title}} />
|
|
176
|
+
|
|
177
|
+
// ✅ Vue (automatically escapes)
|
|
178
|
+
<div>{{ unit.suggestion.title }}</div>
|
|
179
|
+
|
|
180
|
+
// ❌ Vue (dangerous)
|
|
181
|
+
<div v-html="unit.suggestion.title"></div>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Content Security & Phishing
|
|
187
|
+
|
|
188
|
+
Ad content may contain:
|
|
189
|
+
- Phishing links (`action_url: "http://paypa1.com/login"`)
|
|
190
|
+
- Malicious file downloads (`action_url: "javascript:downloadMalware()"`)
|
|
191
|
+
- Social engineering attacks
|
|
192
|
+
|
|
193
|
+
### URL Validation
|
|
194
|
+
|
|
195
|
+
Always validate `action_url` before using it:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { sanitizeURL } from '@the_ro_show/agent-ads-sdk';
|
|
199
|
+
|
|
200
|
+
const safeURL = sanitizeURL(unit.suggestion.action_url);
|
|
201
|
+
|
|
202
|
+
if (!safeURL) {
|
|
203
|
+
console.error('Invalid or dangerous URL:', unit.suggestion.action_url);
|
|
204
|
+
// Don't render the ad
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Safe to use
|
|
209
|
+
window.open(safeURL, '_blank');
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Allowed URL schemes:
|
|
213
|
+
- ✅ `https://`
|
|
214
|
+
- ✅ `http://` (with warning)
|
|
215
|
+
- ✅ `tel:` (phone numbers)
|
|
216
|
+
- ✅ `mailto:` (email)
|
|
217
|
+
- ❌ `javascript:` (blocked)
|
|
218
|
+
- ❌ `data:` (blocked)
|
|
219
|
+
- ❌ `file:` (blocked)
|
|
220
|
+
|
|
221
|
+
### Content Security Policy (CSP)
|
|
222
|
+
|
|
223
|
+
Add CSP headers to your web app:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Input Validation
|
|
232
|
+
|
|
233
|
+
### User Input Sanitization
|
|
234
|
+
|
|
235
|
+
If you pass user input to the SDK (e.g., search queries), sanitize it first:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// ✅ SAFE: Limit length and remove dangerous characters
|
|
239
|
+
function sanitizeQuery(userInput: string): string {
|
|
240
|
+
return userInput
|
|
241
|
+
.slice(0, 500) // Max length
|
|
242
|
+
.replace(/[<>'"]/g, ''); // Remove HTML chars
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const opportunity = createOpportunity({
|
|
246
|
+
taxonomy: 'local_services.movers.quote',
|
|
247
|
+
query: sanitizeQuery(userProvidedQuery),
|
|
248
|
+
// ...
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Taxonomy Validation
|
|
253
|
+
|
|
254
|
+
Only use known, trusted taxonomies:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const ALLOWED_TAXONOMIES = [
|
|
258
|
+
'local_services.movers.quote',
|
|
259
|
+
'local_services.restaurants.search',
|
|
260
|
+
// ...your list
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
function validateTaxonomy(taxonomy: string): boolean {
|
|
264
|
+
return ALLOWED_TAXONOMIES.includes(taxonomy);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ✅ Validate before use
|
|
268
|
+
if (!validateTaxonomy(userTaxonomy)) {
|
|
269
|
+
throw new Error('Invalid taxonomy');
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Rate Limiting & Abuse Prevention
|
|
276
|
+
|
|
277
|
+
### SDK-Side Limits
|
|
278
|
+
|
|
279
|
+
The SDK has **no built-in rate limiting**. You must implement this:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { RateLimiter } from 'limiter';
|
|
283
|
+
|
|
284
|
+
const limiter = new RateLimiter({
|
|
285
|
+
tokensPerInterval: 100,
|
|
286
|
+
interval: 'minute',
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
async function getAd() {
|
|
290
|
+
await limiter.removeTokens(1);
|
|
291
|
+
return client.decide({...});
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Prevent Tracking Abuse
|
|
296
|
+
|
|
297
|
+
Don't allow users to trigger tracking events directly:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// ❌ DANGEROUS: User controls tracking
|
|
301
|
+
app.post('/track-impression', async (req) => {
|
|
302
|
+
await client.trackImpression(req.body); // User can fake metrics!
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ✅ SAFE: Server validates before tracking
|
|
306
|
+
app.post('/track-impression', async (req) => {
|
|
307
|
+
const { unitId } = req.body;
|
|
308
|
+
|
|
309
|
+
// Verify this unit was actually shown to this user
|
|
310
|
+
if (!isValidImpressionForUser(req.session.userId, unitId)) {
|
|
311
|
+
return res.status(403).json({ error: 'Invalid impression' });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
await client.trackImpression({...});
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Request Deduplication
|
|
319
|
+
|
|
320
|
+
Prevent duplicate tracking:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
const impressionsSeen = new Set();
|
|
324
|
+
|
|
325
|
+
async function trackOnce(unitId: string) {
|
|
326
|
+
if (impressionsSeen.has(unitId)) {
|
|
327
|
+
return; // Already tracked
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
impressionsSeen.add(unitId);
|
|
331
|
+
await client.trackImpression({...});
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
41
336
|
|
|
42
337
|
## Reporting Security Issues
|
|
43
338
|
|
|
44
|
-
If you discover a security vulnerability in this SDK, please email
|
|
339
|
+
If you discover a security vulnerability in this SDK, please email:
|
|
340
|
+
|
|
341
|
+
**security@attentionmarket.com**
|
|
45
342
|
|
|
343
|
+
Include:
|
|
46
344
|
- Description of the vulnerability
|
|
47
345
|
- Steps to reproduce
|
|
48
346
|
- Potential impact
|
|
347
|
+
- Your contact information (for follow-up)
|
|
348
|
+
|
|
349
|
+
**Do not:**
|
|
350
|
+
- Open public GitHub issues for security vulnerabilities
|
|
351
|
+
- Post on social media or forums
|
|
352
|
+
- Exploit the vulnerability
|
|
353
|
+
|
|
354
|
+
We will acknowledge your report within 48 hours and provide updates on the fix timeline.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Security Checklist
|
|
359
|
+
|
|
360
|
+
Before deploying to production:
|
|
361
|
+
|
|
362
|
+
- [ ] API keys stored in environment variables (not code)
|
|
363
|
+
- [ ] SDK only used server-side (not in browser/mobile)
|
|
364
|
+
- [ ] All ad content sanitized before rendering (XSS prevention)
|
|
365
|
+
- [ ] URLs validated before use (phishing prevention)
|
|
366
|
+
- [ ] Rate limiting implemented (abuse prevention)
|
|
367
|
+
- [ ] User input validated and sanitized
|
|
368
|
+
- [ ] Error messages don't leak sensitive information
|
|
369
|
+
- [ ] HTTPS enforced for all API calls
|
|
370
|
+
- [ ] Dependencies regularly updated
|
|
371
|
+
- [ ] Security headers configured (CSP, X-Frame-Options, etc.)
|
|
372
|
+
|
|
373
|
+
---
|
|
49
374
|
|
|
50
|
-
|
|
375
|
+
**Last Updated:** 2025-02-01
|
|
376
|
+
**SDK Version:** 0.1.1+
|