| name | speak-security-basics |
| description | Apply Speak security best practices for secrets, user data, and audio handling.
Use when securing API keys, implementing user privacy controls,
or auditing Speak security configuration.
Trigger with phrases like "speak security", "speak secrets",
"secure speak", "speak API key security", "speak privacy".
|
| allowed-tools | Read, Write, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Speak Security Basics
Overview
Security best practices for Speak API keys, user data, and audio content in language learning applications.
Prerequisites
- Speak SDK installed
- Understanding of environment variables
- Access to Speak dashboard
- Knowledge of audio privacy concerns
Instructions
Step 1: Configure Environment Variables
SPEAK_API_KEY=sk_live_***
SPEAK_APP_ID=app_***
SPEAK_WEBHOOK_SECRET=whsec_***
.env
.env.local
.env.*.local
*.wav
*.mp3
recordings/
Step 2: Implement Secret Rotation
export SPEAK_API_KEY="new_key_here"
curl -X POST https://api.speak.com/v1/health \
-H "Authorization: Bearer ${SPEAK_API_KEY}" \
-H "X-App-ID: ${SPEAK_APP_ID}"
Step 3: Apply Least Privilege Access
| Environment | Recommended Scopes |
|---|
| Development | lessons:read, speech:analyze |
| Staging | lessons:read, lessons:write, speech:analyze |
| Production | Only required scopes for features |
const lessonClient = new SpeakClient({
apiKey: process.env.SPEAK_LESSON_API_KEY!,
appId: process.env.SPEAK_APP_ID!,
});
const speechClient = new SpeakClient({
apiKey: process.env.SPEAK_SPEECH_API_KEY!,
appId: process.env.SPEAK_APP_ID!,
});
Step 4: User Audio Privacy
interface AudioPrivacyConfig {
retention: 'session_only' | '30_days' | 'permanent';
shareWithSpeak: boolean;
allowPlayback: boolean;
encryption: 'at_rest' | 'in_transit' | 'both';
}
class SecureAudioHandler {
private config: AudioPrivacyConfig;
constructor(config: AudioPrivacyConfig) {
this.config = config;
}
async storeAudio(userId: string, audioData: ArrayBuffer): Promise<string> {
const encrypted = await this.encrypt(audioData);
const audioId = crypto.randomUUID();
const expiry = this.config.retention ===
? .() + * *
: .. ===
? .() + * * * *
: ;
storage.(audioId, encrypted, { expiry });
({
: ,
userId,
audioId,
: ..,
});
audioId;
}
(: ): <> {
audioIds = storage.(userId);
( id audioIds) {
storage.(id);
}
({
: ,
userId,
: audioIds.,
});
}
(: ): <> {
key = crypto..(
,
.(process..!, ),
,
,
[]
);
iv = crypto.( ());
encrypted = crypto..(
{ : , iv },
key,
data
);
result = (iv. + encrypted.);
result.(iv);
result.( (encrypted), iv.);
result.;
}
}
Step 5: Webhook Signature Verification
import crypto from 'crypto';
function verifySpeakWebhook(
payload: string,
signature: string,
timestamp: string
): boolean {
const secret = process.env.SPEAK_WEBHOOK_SECRET!;
const timestampAge = Date.now() - parseInt(timestamp) * 1000;
if (timestampAge > 300000) {
console.error('Webhook timestamp too old');
return false;
}
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=`)
);
}
() {
signature = req.[] ;
timestamp = req.[] ;
(!(req..(), signature, timestamp)) {
res.().({ : });
}
();
}
Step 6: User Data Protection
interface UserLearningData {
lessonHistory: LessonRecord[];
pronunciationScores: PronunciationRecord[];
vocabularyProgress: VocabularyProgress[];
audioRecordings: AudioReference[];
}
class UserDataManager {
async exportUserData(userId: string): Promise<UserLearningData> {
const [lessons, scores, vocab, audio] = await Promise.all([
db.lessons.findByUser(userId),
db.pronunciation.findByUser(userId),
db.vocabulary.findByUser(userId),
storage.listAudioByUser(userId),
]);
await auditLog({
action: 'user_data_export',
userId,
timestamp: new Date(),
});
return {
lessonHistory: lessons,
pronunciationScores: scores,
vocabularyProgress: vocab,
audioRecordings: audio,
};
}
async deleteUserData(userId: ): <> {
results = .([
db..(userId),
db..(userId),
db..(userId),
.(userId),
speakClient..(userId),
]);
({
: ,
userId,
: (),
: results.( r.),
});
{
: results.( r. === ),
: (),
};
}
}
Security Checklist
API Security
Audio Security
User Data
Output
- Secure API key storage
- Environment-specific access controls
- Audio privacy protection
- Webhook security enabled
- User data protection compliance
Error Handling
| Security Issue | Detection | Mitigation |
|---|
| Exposed API key | Git scanning, audit logs | Rotate immediately |
| Excessive scopes | Audit logs review | Reduce permissions |
| Missing rotation | Key age check | Schedule rotation |
| Audio leak | Access logs | Encrypt and restrict |
| Missing consent | Compliance audit | Update consent flow |
Examples
Service Account Pattern
const clients = {
lessons: new SpeakClient({
apiKey: process.env.SPEAK_LESSON_KEY,
appId: process.env.SPEAK_APP_ID,
}),
speech: new SpeakClient({
apiKey: process.env.SPEAK_SPEECH_KEY,
appId: process.env.SPEAK_APP_ID,
}),
};
Audit Logging
interface AuditEntry {
timestamp: Date;
action: string;
userId: string;
resource: string;
result: 'success' | 'failure';
metadata?: Record<string, any>;
}
async function auditLog(entry: Omit<AuditEntry, 'timestamp'>): Promise<void> {
const log: AuditEntry = { ...entry, timestamp: new Date() };
await auditDb.insert(log);
console.log('[AUDIT]', JSON.stringify(log));
}
Resources
Next Steps
For production deployment, see speak-prod-checklist.