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.

💡 Tip: The core principle of defensive programming is "trust no external input" — user input, tool return values, API responses all need validation. Your plugin crashing is acceptable; your plugin causing data loss or credential leakage is not.

📋 Prerequisites: Completed 13-effect.md and 29-sandbox.md

1. What You'll Learn


Defensive Patterns

Postmortem

2. Credential Management Best Practices

(1) ▶ Example 1

TYPESCRIPT
// ❌ 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

TYPESCRIPT
// ✅ 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

TYPESCRIPT
// ✅ 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

TYPESCRIPT
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

TYPESCRIPT
// ❌ 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

TYPESCRIPT
// ❌ 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

TYPESCRIPT
// ❌ 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

TYPESCRIPT
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

TYPESCRIPT
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

TEXT 📖 Display only
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

TYPESCRIPT
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

TYPESCRIPT
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

MARKDOWN
# 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

TEXT 📖 Display only
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

100%
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

BASH
# 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

Q Does defensive programming slow down code?
A Validation logic's runtime overhead is usually negligible. Security issue remediation costs far exceed prevention costs.
Q Does every tool need approval?
A No. Read-only operations can use always approval. Operations with side effects (write files, execute commands, send requests) need approval.
Q How to handle non-recoverable cleanup failures?
A Log the error and continue cleaning other resources. Cordis internally wraps each cleanup function in try-catch; one failure doesn't prevent others.
Q How often should incident reviews happen?
A Immediately after every P0/P1 incident. P2 incidents can be batched for periodic review. Review incident patterns regularly (e.g., monthly).
Q Does the security audit checklist apply to all plugins?
A Yes. The checklist items are universal security requirements. Different plugins may need additional checks (e.g., SQL injection protection for database plugins).
Q How to test cleanup guarantees?
A Repeatedly load/unload the plugin while monitoring resource usage: 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


📝 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.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏