[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-77273":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":14,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":15,"rankGlobal":8,"rankLanguage":8,"license":8,"archived":16,"fork":16,"defaultBranch":17,"hasWiki":18,"hasPages":16,"topics":19,"createdAt":8,"pushedAt":8,"updatedAt":20,"readmeContent":21,"aiSummary":22,"trendingCount":14,"starSnapshotCount":14,"syncStatus":11,"lastSyncTime":23,"discoverSource":24},77273,"saboteur","vadimsemenykv\u002Fsaboteur","vadimsemenykv",null,"Go",140,2,5,1,0,1.43,false,"main",true,[],"2026-06-12 02:03:42","# Saboteur — HTTP Fault Injection Proxy\n\nA lightweight HTTP-layer fault injection proxy. Intercepts traffic and injects configurable faults based on rules — useful for testing service resilience in development and CI\u002FCD.\n\n**Key differentiator from Toxiproxy:** operates at HTTP layer — understands URLs, methods, headers, and response bodies. Not TCP-level.\n\n---\n\n## Quickstart\n\n```bash\ndocker run -p 8080:8080 -p 8081:8081 \\\n  -e UPSTREAM_URL=http:\u002F\u002Fmy-service:3000 \\\n  yourname\u002Fsaboteur\n```\n\n- **Port 8080** — proxy (send your real traffic here)\n- **Port 8081** — control UI + API (manage rules, view traffic)\n\nOpen `http:\u002F\u002Flocalhost:8081` in your browser to use the web UI.\n\n---\n\n## With a config file\n\n```bash\ndocker run -p 8080:8080 -p 8081:8081 \\\n  -v $(pwd)\u002Fconfig\u002Fexample.yaml:\u002Fetc\u002Fsaboteur\u002Fconfig.yaml \\\n  yourname\u002Fsaboteur --config \u002Fetc\u002Fsaboteur\u002Fconfig.yaml\n```\n\n---\n\n## Docker Compose\n\n```bash\ndocker compose up\n```\n\nUses `docker-compose.yml` in the repo root. Spins up `saboteur` + a mock upstream (`hashicorp\u002Fhttp-echo`).\n\n---\n\n## Configuration Reference\n\nAll settings can be provided via YAML file or environment variables.\n\n| YAML key | Env var | Default | Description |\n|----------|---------|---------|-------------|\n| `proxy.upstream_url` | `UPSTREAM_URL` | *(required)* | URL to proxy traffic to |\n| `proxy.port` | `PROXY_PORT` | `8080` | Proxy listener port |\n| `proxy.timeout_ms` | — | `30000` | Upstream request timeout |\n| `proxy.max_idle_conns` | — | `100` | Max idle upstream connections |\n| `control.port` | `CONTROL_PORT` | `8081` | Control API + UI port |\n| `control.bind` | `CONTROL_BIND` | `0.0.0.0` | Control bind address |\n| `control.api_key` | `API_KEY` | *(none)* | If set, require `X-API-Key` header on control API |\n| `traffic_log.max_entries` | `TRAFFIC_LOG_SIZE` | `1000` | Ring-buffer size |\n| `log_level` | `LOG_LEVEL` | `info` | `debug` \u002F `info` \u002F `warn` \u002F `error` |\n| `log_format` | `LOG_FORMAT` | `json` | `json` \u002F `text` |\n\n---\n\n## Fault Types\n\n### Latency\n\nAdd delay before forwarding or returning.\n\n```yaml\nfault:\n  type: latency\n  mode: fixed       # fixed | uniform | normal\n  fixed_ms: 500\n  apply_to: request # request | response\n```\n\n### Error Response\n\nReturn a specific status without hitting upstream.\n\n```yaml\nfault:\n  type: error\n  status_code: 503\n  body: '{\"error\":\"down\"}'\n  headers:\n    Retry-After: \"30\"\n```\n\n### Connection Abort\n\nDrop the connection after N bytes.\n\n```yaml\nfault:\n  type: abort\n  after_bytes: 0    # 0 = drop immediately\n```\n\n### Timeout\n\nAccept connection, forward to upstream, never respond.\n\n```yaml\nfault:\n  type: timeout\n```\n\n### Body Corruption\n\nReplace the upstream response body.\n\n```yaml\nfault:\n  type: body_corrupt\n  mode: json_invalid  # empty | random_bytes | json_invalid | truncate\n  truncate_bytes: 100 # for truncate mode\n```\n\n### Header Injection\n\nAdd or override headers on request or response.\n\n```yaml\nfault:\n  type: header_inject\n  apply_to: response   # request | response\n  headers:\n    X-Injected: \"true\"\n```\n\n### Bandwidth Throttle\n\nRate-limit response streaming.\n\n```yaml\nfault:\n  type: throttle\n  bytes_per_second: 1024\n```\n\n---\n\n## Rule System\n\nRules are evaluated in priority order (lower number = higher priority). A request matches the **first** rule where all matcher conditions are satisfied.\n\n```yaml\nrules:\n  - id: \"payment-errors\"\n    enabled: true\n    priority: 10\n    description: \"503 on 20% of EU payment POSTs\"\n    match:\n      path_prefix: \"\u002Fapi\u002Fpayment\"\n      methods: [POST]\n      headers:\n        X-Region: \"EU\"\n    percentage: 20        # 0-100; fraction of matching requests that get the fault\n    fault:\n      type: error\n      status_code: 503\n      body: '{\"error\":\"unavailable\"}'\n```\n\n### Matcher fields (all optional; unset = match anything)\n\n| Field | Description |\n|-------|-------------|\n| `path` | Exact URL path |\n| `path_prefix` | URL path prefix |\n| `path_regex` | RE2 regex on URL path |\n| `methods` | HTTP methods (empty = all) |\n| `headers` | All listed headers must match |\n| `query_params` | All listed query params must match |\n\n---\n\n## Control API Overview\n\nBase URL: `http:\u002F\u002Flocalhost:8081`\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `\u002Fhealth` | Health check |\n| GET | `\u002Fmetrics` | Prometheus metrics |\n| GET | `\u002Fapi\u002Frules` | List all rules |\n| POST | `\u002Fapi\u002Frules` | Create rule |\n| GET | `\u002Fapi\u002Frules\u002F{id}` | Get rule |\n| PUT | `\u002Fapi\u002Frules\u002F{id}` | Replace rule |\n| PATCH | `\u002Fapi\u002Frules\u002F{id}` | Update fields (enabled, priority, percentage) |\n| DELETE | `\u002Fapi\u002Frules\u002F{id}` | Delete rule |\n| POST | `\u002Fapi\u002Frules\u002Freset` | Delete all runtime rules (preserves config-file rules) |\n| GET | `\u002Fapi\u002Ftraffic` | Traffic log (`?limit=100&path_filter=&fault_only=true`) |\n| DELETE | `\u002Fapi\u002Ftraffic` | Clear traffic log |\n| GET | `\u002Fapi\u002Ftraffic\u002Fstream` | SSE stream of live traffic |\n| GET | `\u002Fapi\u002Fconfig` | Current effective configuration |\n\nFull spec: [`openapi.yaml`](openapi.yaml)\n\n### API Key auth\n\n```bash\ncurl -H \"X-API-Key: mysecret\" http:\u002F\u002Flocalhost:8081\u002Fapi\u002Frules\n```\n\n### Quick examples\n\n```bash\n# Add an error rule\ncurl -X POST http:\u002F\u002Flocalhost:8081\u002Fapi\u002Frules \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"id\":\"test-error\",\"enabled\":true,\"priority\":1,\"percentage\":100,\"match\":{},\"fault\":{\"type\":\"error\",\"status_code\":503}}'\n\n# Toggle a rule off\ncurl -X PATCH http:\u002F\u002Flocalhost:8081\u002Fapi\u002Frules\u002Ftest-error \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"enabled\":false}'\n\n# View recent traffic (faults only)\ncurl \"http:\u002F\u002Flocalhost:8081\u002Fapi\u002Ftraffic?limit=10&fault_only=true\"\n\n# Delete a rule\ncurl -X DELETE http:\u002F\u002Flocalhost:8081\u002Fapi\u002Frules\u002Ftest-error\n\n# Reset all runtime rules\ncurl -X POST http:\u002F\u002Flocalhost:8081\u002Fapi\u002Frules\u002Freset\n```\n\n---\n\n## Hot Reload (SIGHUP)\n\nSend `SIGHUP` to reload the config file without restarting:\n\n```bash\nkill -HUP $(pgrep saboteur)\n```\n\nConfig-file rules are replaced atomically. Runtime rules (created via API) are preserved.\n\n---\n\n## Prometheus Metrics\n\nAvailable at `http:\u002F\u002Flocalhost:8081\u002Fmetrics`.\n\n| Metric | Labels | Description |\n|--------|--------|-------------|\n| `fault_proxy_requests_total` | method, path_pattern, status_code, fault_type | Total proxied requests |\n| `fault_proxy_faults_total` | rule_id, fault_type | Requests where a fault was injected |\n| `fault_proxy_request_duration_ms` | quantile | End-to-end latency (includes injected latency) |\n| `fault_proxy_upstream_duration_ms` | quantile | Upstream-only latency |\n| `fault_proxy_rules_active` | — | Number of enabled rules |\n| `fault_proxy_upstream_healthy` | — | 1 = reachable, 0 = unreachable |\n\n---\n\n## Building from source\n\n```bash\ngo build -o saboteur .\u002Fcmd\u002Fsaboteur\nUPSTREAM_URL=http:\u002F\u002Flocalhost:3000 .\u002Fsaboteur\n```\n\n### Run tests\n\n```bash\ngo test .\u002F...\ngo test -race .\u002F...\n```\n\n### Build Docker image\n\n```bash\ndocker build -f docker\u002FDockerfile -t saboteur .\n```\n\nMulti-arch:\n\n```bash\ndocker buildx build --platform linux\u002Famd64,linux\u002Farm64 \\\n  -f docker\u002FDockerfile -t yourname\u002Fsaboteur:latest --push .\n```\n\n---\n\n## Out of scope\n\n- TCP-level fault injection (use [Toxiproxy](https:\u002F\u002Fgithub.com\u002FShopify\u002Ftoxiproxy))\n- gRPC support\n- Persistent rule storage (config file is the persistence layer)\n- TLS termination on the proxy port\n","Saboteur 是一个轻量级的HTTP层故障注入代理工具，用于拦截流量并根据规则注入可配置的故障，以测试服务在开发和CI\u002FCD环境中的弹性。它与Toxiproxy的主要区别在于，Saboteur工作于HTTP层，能够理解URL、方法、头部以及响应体，而不仅仅是在TCP层面操作。通过Docker容器运行，Saboteur提供了包括延迟、错误响应、连接中断等多种类型的故障模拟功能，并支持通过Web UI或API进行管理和查看流量。该工具非常适合需要验证微服务架构下各个组件之间通信稳定性的场景使用。","2026-06-11 03:55:15","CREATED_QUERY"]