Lock Down Permissions and Make Production Debuggable
Over-scoped bots are security incidents waiting to happen. When your bot has users:read.email but never uses it, you're one compromised token away from a data breach. Meanwhile, unstructured console.log statements are useless when debugging production issues during a critical customer demo. You need minimal scopes and searchable logs.
Outcome
Create a scope truth table, trim unused permissions, and implement structured logging that tracks every AI operation with searchable fields.
Fast Track
- Generate scope truth table from manifest and code usage
- Remove unused scopes and reinstall to verify failures
- Replace all
console.logwith structuredapp.loggercalls
Building on Previous Lessons
We're extending the observability foundation to production scale:
- From correlation middleware: Correlation middleware forms the base - now we add AI-specific metrics (model, tokens, latency)
- From repository flyover: Context fetching operations get structured logging to track API quotas and rate limits
- From AI tools: Tool executions log inputs and outputs for debugging AI decision chains
- From status communication: Status updates become searchable events for measuring user-perceived latency
- From error handling: Retry attempts, backoff delays, and model fallbacks are tracked for reliability metrics
- Production reasoning: Distributed serverless functions need structured logs - correlation IDs are the only way to trace a request across multiple function executions
- Cost tracking: Token counts in logs enable chargeback models (per-user/per-channel cost attribution)
Hands-On Exercise 5.2
Audit your bot's OAuth scopes and implement production-grade structured logging:
Requirements:
- Create a scope truth table mapping each scope to its usage
- Remove all unused scopes from manifest
- Test with removed scopes to confirm expected failures
- Implement structured logging schema for all AI operations
- Include correlation ID, tokens, latency, and retry info
Implementation hints:
- Use
ast-grepto find all Slack API calls and their required scopes - Test scope removal by commenting out, reinstalling, and verifying failure
- Add structured fields to every
app.loggercall - Include both prompt and completion tokens for cost tracking
Logging schema to implement:
interface StructuredLog {
correlationId: string;
operation: 'respondToMessage' | 'toolCall' | 'retry';
model?: string;
promptTokens?: number;
completionTokens?: number;
retryAttempt?: number;
rateLimitWaitMs?: number;
channel?: string;
thread_ts?: string;
latencyMs?: number;
error?: string;
}Try It
-
Generate scope truth table:
# Find all Slack API calls ast-grep --pattern 'client.$METHOD($$$)' server/Create
/slack-agent/SCOPE_AUDIT.md:# OAuth Scope Audit | Scope | Used By | Can Remove? | |-------|---------|-------------| | app_mentions:read | app-mention.ts:12 - Event subscription | ❌ Required | | channels:history | respond-to-message.ts:34 - Thread context | ❌ Required | | chat:write | All responses | ❌ Required | | groups:history | Private channel support | ✅ Not used | | im:history | DM support | ❌ Required | | mpim:history | Group DM support | ✅ Not used | | users:read | None found | ✅ Remove | | users:read.email | None found | ✅ Remove | -
Test scope removal:
/slack-agent/manifest.json"bot": [ "app_mentions:read", "channels:history", "chat:write", "im:history" // Removed: groups:history, mpim:history, users:read ]After reinstall, test in private channel:
[ERROR] bolt-app { correlationId: 'ev09E5_1234567890.123456', error: 'missing_scope', required: 'groups:history', channel: 'G09D4DG727P' } Cannot read private channel history -
Implement structured logging:
/slack-agent/server/lib/ai/respond-to-message.tsconst response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: conversationHistory, tools: toolDefinitions, stream: true }); app.logger.info({ ...context.correlation, operation: 'respondToMessage', model: 'gpt-4o-mini', promptTokens: response.usage?.prompt_tokens, completionTokens: response.usage?.completion_tokens, retryAttempt: retryCount, rateLimitWaitMs: rateLimitDelay, channel: event.channel, thread_ts: event.thread_ts || event.ts, latencyMs: Date.now() - startTime }, 'AI response generated'); -
Verify structured logs in Vercel:
{ "level": 30, "time": 1733456789123, "msg": "AI response generated", "correlationId": "ev09E5EDA89M_1234567890.123456", "operation": "respondToMessage", "model": "gpt-4o-mini", "promptTokens": 234, "completionTokens": 89, "retryAttempt": 0, "rateLimitWaitMs": 0, "channel": "C09D4DG727P", "thread_ts": "1234567890.123456", "latencyMs": 1247 } -
Query logs by correlation ID:
Vercel Logs Filter: correlationId:"ev09E5EDA89M_1234567890.123456" Results (4 entries): 1. [INFO] Processing app_mention event 2. [INFO] Thread context retrieved (7 messages) 3. [INFO] Tool call: searchKnowledgeBase 4. [INFO] AI response generated (1247ms)
Troubleshooting
Missing Scope Errors:
- Check exact error message for required scope
- Some scopes have dependencies (e.g.,
channels:historyneedschannels:read) - Private channels need
groups:scopes even if not explicitly used
Structured Logging Not Working:
- Ensure you're using
app.loggernotconsole.log - Vercel may need
NODE_ENV=productionfor proper log levels - Check log level configuration in production
Token Counts Missing: For streaming responses, collect usage after stream completes:
let usage = { prompt_tokens: 0, completion_tokens: 0 };
for await (const chunk of stream) {
if (chunk.usage) {
usage = chunk.usage;
}
}Commit
git add -A
git commit -m "feat(security): minimize OAuth scopes and add structured logging
- Create scope audit table with usage mapping
- Remove unused scopes: groups:history, mpim:history, users:read
- Implement structured logging schema with AI metrics
- Add correlation ID, token counts, and latency tracking
- Replace console.log with app.logger throughout"Done-When
- Scope truth table created in
SCOPE_AUDIT.md - Unused scopes removed from manifest
- Bot fails gracefully when accessing removed scopes
- All AI operations log structured data
- Logs queryable by correlation ID in Vercel
Solution
- Example manifest with trimmed scopes:
{
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"channels:history",
"chat:write",
"im:history"
]
}
}
}The trimmed scope list above is a minimal example for a very simple bot, not a literal copy-paste for your course project.
Your bot from earlier lessons needs additional scopes like assistant:write (assistant threads), reactions:write / reactions:read (reaction tools and listeners), and commands (slash commands), plus the appropriate channels:* / groups:* / mpim:* read scopes for the events you actually subscribed to.
Use your own SCOPE_AUDIT.md and manifest.json as the source of truth, and only remove scopes that your audit marks as unused.
- Structured logging middleware enhancement:
export const correlationMiddleware: Middleware<SlackEventMiddlewareArgs> = async ({
context,
body,
next,
logger
}) => {
const event = 'event' in body ? body.event : null;
const correlationId = event
? `${event.type}_${event.event_id}_${Date.now()}`
: `req_${Date.now()}`;
const correlation = {
correlationId,
event_id: event?.event_id,
type: event?.type,
channel: event?.channel,
ts: event?.ts,
thread_ts: event?.thread_ts ?? event?.ts,
user: event?.user
};
// Attach correlation to context so all loggers can spread it
// logger.info({ ...context.correlation, ... }, 'message')
// logger.error({ ...context.correlation, error }, 'message')
(context as any).correlation = correlation;
await next();
};- AI operation logging:
// Inside your existing respondToMessage implementation (with cost control from 4.5)
const startTime = Date.now();
// ...existing AI request code (generateText / withRetry / cost tracking)...
app.logger.info("AI response completed", {
...correlation,
operation: "respondToMessage",
// keep any existing cost fields from lesson 4.5 (actualCost, estimatedCost, estimationAccuracy, ...)
promptTokens: result.usage.promptTokens,
completionTokens: result.usage.completionTokens,
channel: event.channel,
thread_ts: thread_ts || event.ts,
latencyMs: Date.now() - startTime,
});
// In your catch block:
app.logger.error("AI response failed", {
...correlation,
operation: "respondToMessage",
error: error instanceof Error ? error.message : String(error),
channel: event.channel,
thread_ts: thread_ts || event.ts,
latencyMs: Date.now() - startTime,
});Key Takeaways
- Every OAuth scope is a security risk - only keep what you actually use
- Structured logging with correlation IDs makes debugging possible at scale
- Token tracking is essential for cost management
- Scope audits should be part of your CI/CD pipeline
- Use
app.loggereverywhere -console.logis dead to us
Was this helpful?