DeepSeek Harness: Defensive Programming and Incident Review
Last updated: 2026-08-31
An Agent framework's power means errors have greater impact too — an uncleaned timer can leak memory, a hardcoded API key can leak to logs, an unvalidated tool result can cause an Agent to make wrong decisions. Defensive programming isn't optional — it's mandatory.
📋 Prerequisites: Completed 13-effect.md and 29-sandbox.md
1. What You'll Learn
- Credential management best practices
- Result reporting and validation
- Cleanup guarantee
- Side-effect boundaries
- Incident review culture
- Security audit checklist
2. Credential Management Best Practices
(1) ▶ Example 1
// ❌ Hardcoded API Key
const apiKey = 'sk-abc123def456'
// ❌ API Key written to logs
ctx.logger.info(`connecting with key: ${apiKey}`)
// ❌ API Key in URL
const url = `https://api.example.com?key=${apiKey}`
// ❌ API Key in error message
throw new Error(`Authentication failed for key: ${apiKey}`)
(2) ▶ Example 2
// ✅ Read from configuration
export const Config = Schema.object({
apiKey: Schema.string().required().hidden()
})
export function apply(ctx: Context) {
const apiKey = ctx.config.apiKey
// apiKey only used within apply, not leaked externally
}
// ✅ Use environment variables
const apiKey = process.env.MY_PLUGIN_API_KEY
// ✅ Pass via request header (not in URL)
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${apiKey}` }
})
(3) Credential Protection in Logs
// ✅ Hide sensitive info in logs
ctx.logger.info(`connecting to ${endpoint}`) // Don't log the key
// ✅ Custom log filter
function sanitize(obj: any): any {
const sanitized = { ...obj }
if (sanitized.apiKey) sanitized.apiKey = '***'
if (sanitized.authorization) sanitized.authorization = '***'
return sanitized
}
ctx.logger.info('request:', sanitize(request))
(4) Credential Rotation
export const Config = Schema.object({
apiKey: Schema.string().required().hidden(),
keyRotationDays: Schema.number().default(90)
})
export function apply(ctx: Context) {
ctx.setInterval(() => {
const age = getKeyAge()
if (age > ctx.config.keyRotationDays * 86400000) {
ctx.logger.warn('API key is overdue for rotation')
}
}, 86400000)
}
3. Result Reporting and Validation
(1) Validating Tool Return Values
// ❌ Don't validate tool results
async execute({ path }, ctx) {
const result = await ctx.shell.execute(`ls ${path}`)
return result.stdout // May be empty or malformed
}
// ✅ Validate tool results
async execute({ path }, ctx) {
const result = await ctx.shell.execute(`ls ${path}`)
if (result.exitCode !== 0) {
return {
error: true,
message: `ls failed: ${result.stderr}`,
exitCode: result.exitCode
}
}
if (!result.stdout || result.stdout.trim().length === 0) {
return {
error: true,
message: 'Directory is empty or does not exist'
}
}
const files = result.stdout.trim().split('\n')
return { count: files.length, files }
}
(2) Validating API Responses
// ❌ Don't validate API responses
const data = await response.json()
return data.result
// ✅ Validate API responses
async function callAPI(ctx: Context, endpoint: string): Promise<any> {
const response = await fetch(endpoint)
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`)
}
let data: any
try {
data = await response.json()
} catch {
throw new Error('API returned invalid JSON')
}
if (!data || typeof data !== 'object') {
throw new Error('API returned unexpected format')
}
return data
}
(3) Input Validation
// ❌ Don't validate user input
async execute({ command }, ctx) {
return await ctx.shell.execute(command) // Command injection risk
}
// ✅ Validate and sanitize input
async execute({ command }, ctx) {
if (!command || typeof command !== 'string') {
return { error: true, message: 'Invalid command' }
}
if (command.length > 1000) {
return { error: true, message: 'Command too long' }
}
// Whitelist check
const allowed = ['ls', 'cat', 'grep', 'wc', 'head', 'tail']
const baseCommand = command.split(' ')[0]
if (!allowed.includes(baseCommand)) {
return { error: true, message: `Command not allowed: ${baseCommand}` }
}
return await ctx.shell.execute(command)
}
4. Cleanup Guarantee
(1) Cleanup Guarantee Principle
Regardless of how a plugin exits (normal unload, crash, manual stop), all resources must be cleaned up.
(2) Cleanup Checklist
| Resource Type | Cleanup Method | Guarantee Mechanism |
|---|---|---|
| Timers | ctx.setInterval/setTimeout | Auto-cleanup |
| Event listeners | ctx.on() | Auto-cleanup |
| Network connections | ctx.effect() | Manual registration |
| Temp files | ctx.effect() | Manual registration |
| Child processes | ctx.effect() | Manual registration |
| Global state | ctx.effect() | Manual registration |
(3) Cleanup Guarantee Pattern
export function apply(ctx: Context) {
// All resources that need cleanup
const resources: { close: () => void | Promise<void> }[] = []
// Register cleanup immediately when acquiring resource
function acquireResource<T extends { close: () => void | Promise<void> }>(
resource: T
): T {
resources.push(resource)
return resource
}
// Unified cleanup
ctx.effect(() => {
return async () => {
for (const r of resources.reverse()) {
try {
await r.close()
} catch (e) {
ctx.logger.warn('cleanup error:', e)
}
}
}
})
// Usage
const db = acquireResource(openDatabase())
const ws = acquireResource(new WebSocket('ws://localhost:8080'))
}
(4) Temp File Cleanup
export function apply(ctx: Context) {
const tempFiles: string[] = []
ctx.effect(() => {
return async () => {
for (const file of tempFiles.reverse()) {
try {
await ctx.fs.unlink(file)
} catch {}
}
}
})
async function createTempFile(content: string): Promise<string> {
const path = `/tmp/dsh-${Date.now()}-${Math.random().toString(36).slice(2)}`
await ctx.fs.writeFile(path, content)
tempFiles.push(path)
return path
}
}
5. Side-Effect Boundaries
(1) Side-Effect Classification
| Type | Description | Example | Risk |
|---|---|---|---|
| Read-only | Doesn't modify external state | Read file, query database | Low |
| Idempotent write | Repeated execution has same effect | Create file (overwrite if exists) | Medium |
| Non-idempotent write | Repeated execution has different effects | Send email, append log | High |
| Destructive | Irreversible operation | Delete file, DROP TABLE | Very high |
(2) Side-Effect Boundary Principles
Principle 1: Minimize side effects
→ Only execute necessary operations
→ Prefer read-only, then idempotent writes
Principle 2: Side effects should be reversible
→ Save original state before writing
→ Provide undo operations
Principle 3: Side effects should be auditable
→ Record details of each side effect
→ Users can view operation history
Principle 4: Side effects require approval
→ Destructive operations must be approved
→ Non-idempotent operations should be approved
(3) Rollback Pattern
interface ReversibleAction {
execute(): Promise<void>
rollback(): Promise<void>
}
class FileEditAction implements ReversibleAction {
private originalContent: string | null = null
constructor(
private path: string,
private newContent: string,
private ctx: Context
) {}
async execute() {
try {
this.originalContent = await this.ctx.fs.readFile(this.path)
} catch {
this.originalContent = null
}
await this.ctx.fs.writeFile(this.path, this.newContent)
}
async rollback() {
if (this.originalContent !== null) {
await this.ctx.fs.writeFile(this.path, this.originalContent)
} else {
await this.ctx.fs.unlink(this.path)
}
}
}
(4) Side-Effect Auditing
const auditLog: AuditEntry[] = []
function audit(action: string, details: any, reversible: boolean) {
auditLog.push({
timestamp: Date.now(),
action,
details,
reversible,
user: 'agent'
})
ctx.emit('audit/action', { action, details, reversible })
}
// Usage
audit('file_edit', { path: 'src/app.ts', operation: 'edit' }, true)
audit('email_send', { to: 'alice@example.com' }, false)
6. Incident Review Culture
(1) Incident Review Template
# Incident Review: [Title]
## Basic Information
- Date: YYYY-MM-DD
- Impact: [Affected features/users]
- Severity: P0/P1/P2
- Handler: [Name]
## Timeline
- HH:MM — [Event 1]
- HH:MM — [Event 2]
- HH:MM — [Fix]
## Root Cause Analysis
[5 Whys analysis]
## Remediation
- Short-term: [Immediate fix]
- Long-term: [Prevention measure]
## Lessons Learned
- [Lesson 1]
- [Lesson 2]
(2) Common Incident Patterns
| Incident Pattern | Root Cause | Prevention |
|---|---|---|
| API Key leak | Credentials printed in logs | Log sanitization |
| Memory leak | Timers not cleaned up | Use ctx.setInterval |
| Data loss | Delete operations without confirmation | Approval policy |
| Infinite loop | Tools calling each other | Call depth limits |
| Cascading failure | Exceptions not isolated | try-catch + independent Fiber |
(3) ▶ Example 3
Incident: Agent deleted user project files
Why 1: Agent executed rm -rf /project
→ Because the tool had no path validation
Why 2: Tool had no path validation
→ Because the developer didn't implement whitelist checking
Why 3: Developer didn't implement whitelist checking
→ Because there was no security review process
Why 4: No security review process
→ Because the team hadn't established a security checklist
Why 5: Team hadn't established a security checklist
→ Because of insufficient security awareness training
Fix: Establish a security audit checklist; all plugins must be checked before publishing
7. Security Audit Checklist
(1) Plugin Security Audit Checklist
| # | Check Item | Category | Priority |
|---|---|---|---|
| 1 | API Key not hardcoded | Credentials | P0 |
| 2 | Sensitive info not in logs | Credentials | P0 |
| 3 | All ctx.effect have cleanup functions | Cleanup | P0 |
| 4 | Timers use ctx.setInterval/setTimeout | Cleanup | P0 |
| 5 | Tool return values have error handling | Validation | P1 |
| 6 | User input is validated and sanitized | Validation | P1 |
| 7 | API response format is validated | Validation | P1 |
| 8 | Destructive operations have approval policy | Side effects | P1 |
| 9 | Non-idempotent operations are reversible | Side effects | P2 |
| 10 | Side effects have audit logs | Side effects | P2 |
| 11 | Permission declarations are complete | Permissions | P1 |
| 12 | No unnecessary permission requests | Permissions | P2 |
| 13 | Dependency versions have compatibility declarations | Compatibility | P2 |
| 14 | No known security vulnerabilities in dependencies | Dependencies | P1 |
(2) Audit Process
graph TD
CODE[Plugin development complete] --> SELF[Developer self-audit]
SELF --> CHECK{All checklist items pass?}
CHECK -->|No| FIX[Fix issues]
FIX --> SELF
CHECK -->|Yes| REVIEW[Team review]
REVIEW --> APPROVE{Review passed?}
APPROVE -->|No| FIX2[Modify code]
FIX2 --> REVIEW
APPROVE -->|Yes| PUBLISH[Publish]
(3) Automated Auditing
# Run security audit
dsh audit my-plugin
# Output
🔒 Security Audit: my-plugin
✅ No hardcoded credentials
✅ All ctx.effect() have cleanup functions
⚠️ Tool 'db_query' has no input validation
❌ API response not validated in 'fetch_data'
✅ Approval policy configured for destructive operations
⚠️ Permission 'shell.execute' may not be necessary
2 errors, 2 warnings found. Fix before publishing.
❓ FAQ
always approval. Operations with side effects (write files, execute commands, send requests) need approval.bash # Loop test for i in {1..100}; do dsh plugin enable my-plugin dsh plugin disable my-plugin done # Check if memory and connection counts remain stable 📖 Summary
- Credential management: no hardcoding, no logging, use config/env vars, rotate regularly
- Result validation: validate tool return values, API responses, user input — trust no external data
- Cleanup guarantee: all resources register cleanup functions, unified management, exceptions don't block other cleanup
- Side-effect boundaries: minimize side effects, make reversible, make auditable, destructive operations need approval
- Incident review: 5 Whys root cause analysis, establish remediation and prevention measures
- Security audit checklist: 14 check items, developer self-audit + team review + automated scanning
📝 Exercises
1. ⭐ Basic: Review your previously written plugin code against the security audit checklist. List issues found and remediation plans.
2. ⭐⭐ Intermediate: Add complete input validation to a tool plugin — check parameter types, length, format, whitelist-filter Shell command parameters. Test: input various illegal parameters and confirm all return meaningful error messages.
3. ⭐⭐⭐ Challenge: Implement a ReversibleAction system — each operation with side effects creates a ReversibleAction object that saves original state on execute and restores on rollback. Write a file edit tool supporting rollback: after editing a file, call undo_last to revert the last edit. Test: edit three times consecutively, roll back sequentially, confirm file returns to its original state.