deepdebug-local-agent 1.0.22 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/context.md +76 -0
- package/.claude/skills/README.md +25 -0
- package/.claude/skills/tdd-debugging/SKILL.md +53 -0
- package/.claude/skills/tdd-debugging/references/anti-patterns.md +43 -0
- package/.claude/skills/tdd-debugging/references/command-reference.md +46 -0
- package/.claude/standards/README.md +51 -0
- package/.claude/standards/clean-code.md +75 -0
- package/.claude/standards/code-review-checklist.md +61 -0
- package/.claude/standards/layered-architecture.md +163 -0
- package/.claude/standards/node.md +45 -0
- package/.claude/standards/security-owasp.md +114 -0
- package/.claude/standards/simplicity.md +42 -0
- package/.claude/standards/solid.md +64 -0
- package/.claude/standards/testing.md +59 -0
- package/.cursor/rules/standards.mdc +17 -0
- package/.github/workflows/build-byoc-image.yml +315 -0
- package/.github/workflows/deploy-gcp.yml +102 -0
- package/.github/workflows/npm-publish.yml +69 -8
- package/AGENTS.md +21 -0
- package/CHANGELOG.md +69 -0
- package/CLAUDE.md +13 -0
- package/CLAUDE_CODE_AGENT_INFINITE.md +5 -3
- package/Dockerfile +4 -0
- package/Dockerfile.full +55 -3
- package/README.md +81 -0
- package/bin/install.js +16 -5
- package/cloudbuild-full.yaml +57 -0
- package/docker-compose.yml +5 -3
- package/docs/OBSERVABILITY.md +164 -0
- package/docs/byoc-agent-image.md +70 -0
- package/docs/graphify-packaging-plan.md +133 -0
- package/docs/graphify-validation-run-2026-10-28.md +243 -0
- package/docs/legacy-connectors.md +268 -0
- package/docs/releasing.md +43 -0
- package/docs/signed-commands.md +163 -0
- package/env.example +30 -0
- package/helm/Chart.yaml +2 -2
- package/helm/templates/deployment.yaml +13 -3
- package/helm/templates/networkpolicy.yaml +22 -0
- package/helm/templates/secret.yaml +1 -0
- package/helm/values.yaml +13 -14
- package/mcp-server.js +4 -1
- package/package.json +12 -4
- package/scripts/sync-fixtures.mjs +246 -0
- package/scripts/upgrade-observability-push.sh +227 -0
- package/src/adapters/as400/as400-parser.js +12 -0
- package/src/adapters/as400/index.js +134 -0
- package/src/adapters/cobol/cobol-parser.js +37 -0
- package/src/adapters/cobol/index.js +151 -0
- package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
- package/src/adapters/ibmmq/index.js +137 -0
- package/src/adapters/index.js +79 -0
- package/src/adapters/oracle-forms/index.js +145 -0
- package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
- package/src/adapters/sap/index.js +146 -0
- package/src/adapters/sap/sap-parser.js +41 -0
- package/src/ai-engine.js +22 -22
- package/src/config/trusted-signing-keys.json +3 -0
- package/src/detectors/build-tool-detector.js +50 -0
- package/src/detectors/multi-project-detector.js +22 -22
- package/src/detectors/service-detector.js +24 -24
- package/src/exec-utils.js +26 -4
- package/src/git/base-git-provider.js +117 -117
- package/src/git/bitbucket-provider.js +2 -2
- package/src/git/git-provider-registry.js +18 -18
- package/src/git/github-provider.js +13 -13
- package/src/git/multi-repo-pr.js +161 -0
- package/src/mcp-http-server.js +32 -16
- package/src/observability/bundle-files.js +418 -0
- package/src/observability/detect.js +151 -0
- package/src/observability/provision.js +212 -0
- package/src/observability/render-cli.js +59 -0
- package/src/observability/routes.js +88 -0
- package/src/patch/patch-engine.js +28 -28
- package/src/patch-manager.js +85 -85
- package/src/patch_bkp.js +14 -14
- package/src/ports.js +4 -4
- package/src/routes/workspace.route.js +47 -16
- package/src/runtimes/base-runtime.js +75 -75
- package/src/runtimes/java/gradle-runtime.js +17 -17
- package/src/runtimes/java/java-integrations.js +25 -25
- package/src/runtimes/java/maven-runtime.js +34 -34
- package/src/runtimes/node/node-integrations.js +1 -1
- package/src/runtimes/node/npm-runtime.js +14 -14
- package/src/runtimes/node/yarn-runtime.js +4 -4
- package/src/runtimes/runtime-registry.js +25 -25
- package/src/server-local.js +4 -4
- package/src/server.js +1412 -335
- package/src/siblings/burst-suppressor.js +89 -0
- package/src/siblings/capabilities.js +273 -0
- package/src/siblings/env-compat.cjs +58 -0
- package/src/siblings/incident-forwarder.js +320 -0
- package/src/siblings/logger.js +15 -0
- package/src/siblings/metrics.js +94 -0
- package/src/utils/agent-auth.js +184 -0
- package/src/utils/approval-token.js +56 -0
- package/src/utils/bind-host.js +29 -0
- package/src/utils/branch-refspec.js +98 -0
- package/src/utils/child-env.js +34 -0
- package/src/utils/environment-diagnostics.js +55 -55
- package/src/utils/exec-policy.js +962 -0
- package/src/utils/exec-utils.js +15 -15
- package/src/utils/file-count.js +46 -0
- package/src/utils/fs-utils.js +10 -10
- package/src/utils/multi-repo-open.js +132 -0
- package/src/utils/open-gate.js +183 -0
- package/src/utils/ranged-read.js +65 -0
- package/src/utils/request-guards.js +77 -0
- package/src/utils/request-signing.js +342 -0
- package/src/utils/root-recovery.js +244 -0
- package/src/utils/safe-exec-args.js +58 -0
- package/src/utils/sandbox.js +182 -0
- package/src/utils/search-outcome.js +56 -0
- package/src/utils/workspace-root.js +80 -0
- package/src/validators/syntax-validator.js +149 -0
- package/src/workspace/detect-port.js +16 -16
- package/src/workspace/file-reader.js +1 -1
- package/src/workspace/process-manager.js +46 -46
- package/src/workspace-manager.js +86 -24
- package/test/adapters/as400/as400.test.js +121 -0
- package/test/adapters/cobol/cobol.test.js +217 -0
- package/test/adapters/ibmmq/ibmmq.test.js +122 -0
- package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
- package/test/adapters/registry.test.js +108 -0
- package/test/adapters/sap/sap.test.js +142 -0
- package/test/agent-auth.test.js +194 -0
- package/test/bind-host.test.js +26 -0
- package/test/branch-refspec.test.js +224 -0
- package/test/burst-suppressor.test.js +60 -0
- package/test/capabilities-access.test.js +47 -0
- package/test/capabilities.test.js +87 -0
- package/test/child-env.test.js +23 -0
- package/test/env-compat.test.js +71 -0
- package/test/exec-policy.test.js +463 -0
- package/test/file-count.test.js +64 -0
- package/test/incident-forwarder.test.js +231 -0
- package/test/multi-repo-open.test.js +180 -0
- package/test/multi-repo-pr.test.js +129 -0
- package/test/observability-detect.test.js +199 -0
- package/test/open-gate.test.js +341 -0
- package/test/ranged-read.test.js +60 -0
- package/test/request-guards.test.js +91 -0
- package/test/request-signing.test.js +284 -0
- package/test/root-recovery-sidecar.test.js +171 -0
- package/test/root-recovery.test.js +274 -0
- package/test/safe-exec-args.test.js +58 -0
- package/test/sandbox.test.js +98 -0
- package/test/search-outcome.test.js +50 -0
- package/test/workspace-root.test.js +64 -0
- package/tools/graphify-validation/README.md +92 -0
- package/tools/graphify-validation/config/questions.json +31 -0
- package/tools/graphify-validation/config/repos.json +9 -0
- package/tools/graphify-validation/config/symbols.json +6 -0
- package/tools/graphify-validation/fixtures/spring/expected.json +34 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AppConfig.java +29 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/AuditLogger.java +15 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/Cache.java +7 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/CheckoutService.java +25 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/InMemoryCache.java +18 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/OrderController.java +22 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaymentGateway.java +5 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/PaypalGateway.java +11 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/RefundService.java +14 -0
- package/tools/graphify-validation/fixtures/spring/src/main/java/com/example/fx/StripeGateway.java +11 -0
- package/tools/graphify-validation/lib/affected_vs_rg.py +81 -0
- package/tools/graphify-validation/lib/audit_hook/sitecustomize.py +22 -0
- package/tools/graphify-validation/lib/graph_metrics.py +96 -0
- package/tools/graphify-validation/lib/gv_common.py +130 -0
- package/tools/graphify-validation/lib/licenses.py +47 -0
- package/tools/graphify-validation/lib/pip_audit_summary.py +18 -0
- package/tools/graphify-validation/lib/recall.py +115 -0
- package/tools/graphify-validation/lib/report.py +226 -0
- package/tools/graphify-validation/lib/run_all.py +237 -0
- package/tools/graphify-validation/lib/run_graphify.py +144 -0
- package/tools/graphify-validation/lib/rusage_helper.py +17 -0
- package/tools/graphify-validation/lib/sample_edges.py +246 -0
- package/tools/graphify-validation/lib/spring_fixture.py +81 -0
- package/tools/graphify-validation/requirements-py311.lock +508 -0
- package/tools/graphify-validation/requirements-tools.in +1 -0
- package/tools/graphify-validation/requirements-tools.lock +371 -0
- package/tools/graphify-validation/requirements.in +1 -0
- package/tools/graphify-validation/requirements.lock +502 -0
- package/tools/graphify-validation/run.sh +43 -0
- package/tools/graphify-validation/tests/test_harness.py +151 -0
- package/cloudbuild-agent-qa.yaml +0 -43
- package/cloudbuild-agent-staging.yaml +0 -40
- package/cloudbuild.yaml.deprecated +0 -42
- package/src/local-agent-source.tar.gz +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Legacy connectors — configuration & troubleshooting
|
|
2
|
+
|
|
3
|
+
Phase 2 of VisionHammer ships five Node.js EventEmitter adapters that ingest
|
|
4
|
+
logs from legacy on-prem systems into the DeepDebug pipeline. This document is
|
|
5
|
+
the operator's reference for enabling and tuning them.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
1. Read [Setup](#setup) to configure your GitHub PAT (only needed if you plan to
|
|
10
|
+
run `npm run sync-fixtures` to refresh test fixtures).
|
|
11
|
+
2. Set `LEGACY_STACKS=<stacks>` in the agent's environment to choose which
|
|
12
|
+
adapters boot. Example: `LEGACY_STACKS=sap,cobol`.
|
|
13
|
+
3. Provide per-adapter config via env or `agent.yaml` (see
|
|
14
|
+
[Configuration](#configuration) below).
|
|
15
|
+
4. Restart the local agent. Adapters start automatically and stop within 5 s on
|
|
16
|
+
`SIGTERM`.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
The `npm run sync-fixtures` script clones the
|
|
21
|
+
`williambella/visionhammer-example-*` GitHub repos to refresh
|
|
22
|
+
`test/fixtures/<stack>/` from upstream. It needs a GitHub PAT.
|
|
23
|
+
|
|
24
|
+
1. On github.com → Developer settings → Personal access tokens → Tokens
|
|
25
|
+
(classic) → Generate new token. Scope: `repo` (read-only is sufficient).
|
|
26
|
+
2. Save the PAT to `~/.config/visionhammer/github-pat` with permissions `0600`:
|
|
27
|
+
```bash
|
|
28
|
+
mkdir -p ~/.config/visionhammer
|
|
29
|
+
echo "YOUR_PAT_HERE" > ~/.config/visionhammer/github-pat
|
|
30
|
+
chmod 600 ~/.config/visionhammer/github-pat
|
|
31
|
+
```
|
|
32
|
+
3. **Never paste** the PAT into a shell env var. The script reads the file
|
|
33
|
+
directly at invocation time and validates the token against `GET /user`
|
|
34
|
+
before any clone. (T-2-04 + T-2-05 mitigation.)
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
Adapters are configured via the `LEGACY_STACKS` env var (which stacks to boot)
|
|
39
|
+
plus per-adapter env vars or `agent.yaml` entries.
|
|
40
|
+
|
|
41
|
+
### Supported stacks
|
|
42
|
+
|
|
43
|
+
| Stack | Adapter dir | Default config | Required mounts/access |
|
|
44
|
+
| -------------- | -------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
45
|
+
| `sap` | `src/adapters/sap/` | `{sapDir: '/usr/sap/PRD', instanceNumber: '00', interval: 5000}` | NFS/SMB mount of `/usr/sap/<SID>/` readable by the agent user |
|
|
46
|
+
| `ibmmq` | `src/adapters/ibmmq/` | `{errorsDir: '/var/mqm/qmgrs/QM_PRD01/errors', interval: 5000}` | Read access to qmgr `errors/` directory |
|
|
47
|
+
| `as400` | `src/adapters/as400/` | `{host: 'as400-prd-01', port: 22, pollInterval: 30000}` | SSH credentials (password OR private-key); user with `DSPLOG` authority |
|
|
48
|
+
| `oracle-forms` | `src/adapters/oracle-forms/` | `{traceDir: '/u01/oracle/forms/trace', interval: 5000}` | Read access to `FORMS_TRACE_DIR` |
|
|
49
|
+
| `cobol` | `src/adapters/cobol/` | `{spoolDir: '/var/spool/jes2', interval: 5000}` | Read access to JES2 spool export dir |
|
|
50
|
+
|
|
51
|
+
Glob anchors (per-adapter, hard-coded):
|
|
52
|
+
|
|
53
|
+
- **SAP** watches `<sapDir>/D<instanceNumber>/work/dev_w*` AND
|
|
54
|
+
`<sapDir>/D<instanceNumber>/log/SLOG*` (T-2-03 mitigation — narrow regex
|
|
55
|
+
surface).
|
|
56
|
+
- **IBM MQ** watches `<errorsDir>/AMQERR*.LOG` then enforces filename anchor
|
|
57
|
+
`^AMQERR0[1-3]\.LOG$` in-handler (chokidar bracket-class workaround on macOS
|
|
58
|
+
/ Node 24 / chokidar 3.6.0).
|
|
59
|
+
- **Oracle Forms** watches `<traceDir>/*.trc` only (`*.log` is **not** read).
|
|
60
|
+
- **COBOL** watches `<spoolDir>/*` with `depth: 0` (broad spool glob — JES2
|
|
61
|
+
exports use arbitrary file names; non-files are filtered with `isFile()`).
|
|
62
|
+
- **AS/400** does NOT watch files; it polls the literal command
|
|
63
|
+
`system "DSPLOG OUTPUT(*PRINT) LOGTYPE(*MSG) MSGTYPE(*ESCAPE *NOTIFY)"` over
|
|
64
|
+
SSH every `pollInterval` ms.
|
|
65
|
+
|
|
66
|
+
### Per-adapter config sub-objects
|
|
67
|
+
|
|
68
|
+
The registry's
|
|
69
|
+
`loadAdapters({legacyStacks, sap, ibmmq, as400, 'oracle-forms', cobol})`
|
|
70
|
+
accepts one sub-object per stack. Each is passed straight to the adapter
|
|
71
|
+
constructor. Override defaults by setting the corresponding key. Example:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { loadAdapters, stopAll } from "./src/adapters/index.js";
|
|
75
|
+
|
|
76
|
+
const adapters = loadAdapters({
|
|
77
|
+
legacyStacks: ['sap', 'cobol'],
|
|
78
|
+
sap: { sapDir: '/sapmnt/PRD', instanceNumber: '01', interval: 10000 },
|
|
79
|
+
cobol: { spoolDir: '/mnt/zos/spool', interval: 30000 },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for (const a of adapters) await a.start();
|
|
83
|
+
// ... later, on shutdown
|
|
84
|
+
await stopAll(adapters, 5000);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`loadAdapters` returns adapters **unstarted** (AD-1 contract). The caller owns
|
|
88
|
+
the lifecycle. The `LEGACY_STACKS` env-var bootstrap path in `src/server.js`
|
|
89
|
+
already wires `loadAdapters` + `start()` for you when the env var is set.
|
|
90
|
+
|
|
91
|
+
## Event shape
|
|
92
|
+
|
|
93
|
+
Every adapter emits `error` events with EXACTLY this shape (frozen, 6 keys, no
|
|
94
|
+
extras — alphabetical key order matters for the failsafe contract test):
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
{
|
|
98
|
+
adapter: 'sap' | 'ibmmq' | 'as400' | 'oracle-forms' | 'cobol',
|
|
99
|
+
errorCode: 'ORA-01403' | 'AMQ7472W' | 'CPF1234' | 'S0C7' | null,
|
|
100
|
+
file: '/usr/sap/PRD/D00/work/dev_w0',
|
|
101
|
+
message: '<verbatim matched line>',
|
|
102
|
+
source: '/usr/sap/PRD/D00/work',
|
|
103
|
+
timestamp: '2024-01-15T09:12:44.000Z'
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Per AD-5, events are ordered by byte position **within a single file**.
|
|
108
|
+
Cross-file and cross-adapter ordering are explicit non-guarantees — the
|
|
109
|
+
downstream gateway pipeline correlates by `incidentId`, not by event sequence.
|
|
110
|
+
|
|
111
|
+
`timestamp` is the moment the adapter detected the line (not the timestamp
|
|
112
|
+
inside the log line — log-line timestamps have inconsistent precision across
|
|
113
|
+
formats).
|
|
114
|
+
|
|
115
|
+
## Sync fixtures script
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Sync all 5 stacks
|
|
119
|
+
npm run sync-fixtures
|
|
120
|
+
|
|
121
|
+
# Sync only a subset
|
|
122
|
+
node scripts/sync-fixtures.mjs --stacks=oracle-forms,cobol
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
What it does, in order, for each requested stack:
|
|
126
|
+
|
|
127
|
+
1. Reads the PAT once from `~/.config/visionhammer/github-pat`.
|
|
128
|
+
2. Validates the PAT via `GET https://api.github.com/user` (T-2-05).
|
|
129
|
+
3. Shallow-clones `williambella/visionhammer-example-<stack>` to
|
|
130
|
+
`/tmp/sync-<stack>` via `git clone --depth 1 --branch main` with the PAT
|
|
131
|
+
embedded in the URL **only as a `child_process.spawn` argv element**, never
|
|
132
|
+
echoed to stdout/stderr/argv-string/env (T-2-04).
|
|
133
|
+
4. Copies `samples/*.log` (case-insensitive) into `test/fixtures/<dir>/`
|
|
134
|
+
(mapping: `sap-abap → sap`, `ibm-mq → ibmmq`, others identity).
|
|
135
|
+
5. Renames IBM MQ fixtures so they pass the adapter's `^AMQERR0[1-3]\.LOG$`
|
|
136
|
+
filename anchor:
|
|
137
|
+
|
|
138
|
+
| Source filename | Target filename |
|
|
139
|
+
| ---------------------------------------- | --------------- |
|
|
140
|
+
| `ibm-mq-sample-mqrc-2033.log` | `AMQERR01.LOG` |
|
|
141
|
+
| `ibm-mq-sample-mqrc-2058.log` | `AMQERR02.LOG` |
|
|
142
|
+
| `ibm-mq-sample-resource-leak.log` | `AMQERR03.LOG` |
|
|
143
|
+
|
|
144
|
+
6. Removes the `/tmp/sync-<stack>` clone scratch dir.
|
|
145
|
+
|
|
146
|
+
Idempotent — re-runs cleanly overwrite prior fixtures and rewipe scratch dirs.
|
|
147
|
+
On git failure the embedded PAT is replaced with `***REDACTED***` before
|
|
148
|
+
stderr is written.
|
|
149
|
+
|
|
150
|
+
## Example fixture repos
|
|
151
|
+
|
|
152
|
+
| Stack | Repo |
|
|
153
|
+
| ----------------- | ------------------------------------------------------------------------------------------ |
|
|
154
|
+
| Oracle Forms 12c | [williambella/visionhammer-example-oracle-forms](https://github.com/williambella/visionhammer-example-oracle-forms) |
|
|
155
|
+
| Oracle PL/SQL | [williambella/visionhammer-example-plsql](https://github.com/williambella/visionhammer-example-plsql) |
|
|
156
|
+
| SAP ABAP | [williambella/visionhammer-example-sap-abap](https://github.com/williambella/visionhammer-example-sap-abap) |
|
|
157
|
+
| COBOL / z/OS | [williambella/visionhammer-example-cobol](https://github.com/williambella/visionhammer-example-cobol) |
|
|
158
|
+
| IBM MQ Java | [williambella/visionhammer-example-ibm-mq](https://github.com/williambella/visionhammer-example-ibm-mq) |
|
|
159
|
+
|
|
160
|
+
Each repo carries: an intentional-bug `README.md`, source code reproducing each
|
|
161
|
+
bug, `samples/<stack>-sample-<n>.log` synthetic logs, and `samples/README.md`
|
|
162
|
+
with `## Source citation`, `## Product version`, and
|
|
163
|
+
`## Zero PII confirmation` sections per AD-6.
|
|
164
|
+
|
|
165
|
+
## Troubleshooting
|
|
166
|
+
|
|
167
|
+
### CPU usage too high (T-2-01)
|
|
168
|
+
|
|
169
|
+
Default poll intervals are conservative (5 s for chokidar-based adapters,
|
|
170
|
+
30 s for AS/400 SSH). If you see elevated CPU on the agent host or the legacy
|
|
171
|
+
system:
|
|
172
|
+
|
|
173
|
+
- Increase `interval` for chokidar adapters (sap, ibmmq, oracle-forms, cobol).
|
|
174
|
+
Try `10000` (10 s) first.
|
|
175
|
+
- Increase `pollInterval` for AS/400. Try `60000` (60 s).
|
|
176
|
+
- Do **not** set `interval < 1000`. NFS/SMB watch polling at sub-second cadence
|
|
177
|
+
is pathological for production systems.
|
|
178
|
+
|
|
179
|
+
### AS/400 SSH session keeps dropping (T-2-02)
|
|
180
|
+
|
|
181
|
+
The AS/400 adapter uses SSH keepalive (`keepaliveInterval: 30000` ×
|
|
182
|
+
`keepaliveCountMax: 3` = 90 s tolerance) and auto-reconnects on `'close'` with
|
|
183
|
+
exponential backoff:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
1 s → 2 s → 4 s → 8 s → 16 s → 32 s → 60 s (cap)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Backoff resets to 1 s on successful re-auth (`'ready'` event).
|
|
190
|
+
|
|
191
|
+
To verify reconnect health: tail the agent log; `'close'` events should be
|
|
192
|
+
followed by a successful `'ready'` within ~60 s. If reconnect storms occur:
|
|
193
|
+
|
|
194
|
+
- Verify the AS/400 SSH server's idle timeout setting. Some SSH daemons close
|
|
195
|
+
sessions despite keepalive; tune the daemon's `ClientAliveInterval` higher
|
|
196
|
+
than 30 s.
|
|
197
|
+
- Check network MTU between the agent and AS/400. Fragmented keepalive packets
|
|
198
|
+
get dropped.
|
|
199
|
+
- The polling cadence (`pollInterval`, default 30 s) is independent of the
|
|
200
|
+
keepalive cadence — they run on separate timers.
|
|
201
|
+
|
|
202
|
+
### `npm run sync-fixtures` fails with "GitHub credentials not configured"
|
|
203
|
+
|
|
204
|
+
Confirm `~/.config/visionhammer/github-pat` exists, is readable by the agent
|
|
205
|
+
user, and contains a valid PAT with `repo` scope. The script reads this file
|
|
206
|
+
once at invocation; it never accepts a PAT from env vars. (T-2-04 mitigation.)
|
|
207
|
+
|
|
208
|
+
### `npm run sync-fixtures` fails with "PAT validation failed"
|
|
209
|
+
|
|
210
|
+
The PAT validity gate (`GET /user`) returned a non-200 status. Causes:
|
|
211
|
+
|
|
212
|
+
- PAT expired or revoked. Regenerate at github.com/settings/tokens.
|
|
213
|
+
- PAT lost `repo` scope. Regenerate.
|
|
214
|
+
- Network failure to api.github.com. Re-run when connectivity returns.
|
|
215
|
+
|
|
216
|
+
### `npm run sync-fixtures` partial failure for one stack
|
|
217
|
+
|
|
218
|
+
The script aborts on first failure (per current implementation) so each clone
|
|
219
|
+
gates the next. Common causes:
|
|
220
|
+
|
|
221
|
+
- Repo deleted from `williambella/visionhammer-example-<stack>`. Recreate by
|
|
222
|
+
re-running Phase 2 / Plan 02-0X (where X = 2..6).
|
|
223
|
+
- Stack name typo in `--stacks=`. Allowed values:
|
|
224
|
+
`oracle-forms,plsql,sap-abap,cobol,ibm-mq`.
|
|
225
|
+
- Network failure mid-clone. Re-run the script.
|
|
226
|
+
|
|
227
|
+
### SIGTERM doesn't shut down adapters cleanly
|
|
228
|
+
|
|
229
|
+
The `SIGTERM` handler in `src/server.js` calls `stopAll(_legacyAdapters, 5000)`
|
|
230
|
+
— adapters have 5 s to release file handles + close SSH. The 5-s budget is
|
|
231
|
+
enforced by an `unref`'d timer racing the `Promise.all` of per-adapter
|
|
232
|
+
`stop()` calls; per-adapter exceptions are swallowed so the shutdown path
|
|
233
|
+
always returns.
|
|
234
|
+
|
|
235
|
+
If a process refuses to exit altogether, that's a separate concern: the
|
|
236
|
+
`local-agent` process keeps an `express` listener (`PORT`) and an MCP HTTP
|
|
237
|
+
listener (`MCP_PORT`) open, plus an optional WebSocket reverse tunnel. Those
|
|
238
|
+
are not closed by the SIGTERM handler today (pre-existing local-agent
|
|
239
|
+
architecture). Set `DISABLE_WS_TUNNEL=true` to drop the WS half; the express
|
|
240
|
+
+ MCP listeners require a separate explicit close path.
|
|
241
|
+
|
|
242
|
+
For the **legacy connector contract specifically** — `stopAll(adapters, 5000)`
|
|
243
|
+
is verified to resolve within 5 s (registry contract test
|
|
244
|
+
`stopAll respects timeout — slow stop does not block forever`).
|
|
245
|
+
|
|
246
|
+
### Adapter logs nothing
|
|
247
|
+
|
|
248
|
+
Possible causes by adapter:
|
|
249
|
+
|
|
250
|
+
- **All chokidar adapters** — TAIL mode is the default. Newly-appended lines
|
|
251
|
+
emit; existing content does not. To replay a file from byte 0 set
|
|
252
|
+
`replayOnStart: true` in the adapter config.
|
|
253
|
+
- **SAP / IBM MQ / Oracle Forms / COBOL** — verify the watched directory
|
|
254
|
+
exists; ENOENT is silent by design (legitimate state for newly-provisioned
|
|
255
|
+
systems).
|
|
256
|
+
- **IBM MQ** — only `AMQERR01.LOG`, `AMQERR02.LOG`, `AMQERR03.LOG` are read.
|
|
257
|
+
`AMQERR04.LOG` and other names are silently dropped at the in-handler anchor
|
|
258
|
+
regex.
|
|
259
|
+
- **AS/400** — verify SSH connectivity (`ssh -p 22 user@as400-prd-01 'system DSPLOG'`).
|
|
260
|
+
ENOENT-equivalent (unreachable host) shows in the agent stderr only on the
|
|
261
|
+
first `'close'` failure; subsequent reconnect attempts are silent.
|
|
262
|
+
|
|
263
|
+
## Field references
|
|
264
|
+
|
|
265
|
+
- 02-SPEC.md: `~/Documents/Projects/visionhammer-agents/.planning/phases/02-legacy-stack-support/02-SPEC.md`
|
|
266
|
+
- Phase 3 (LegacyErrorPatterns Java catalog): consumes `test/fixtures/<stack>/`
|
|
267
|
+
as test fixtures.
|
|
268
|
+
- Phase 6 (E2E smoke): re-uses the example repos as workspaces.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Releasing the local agent
|
|
2
|
+
|
|
3
|
+
Two artefacts are released: the npm package `deepdebug-local-agent` and the
|
|
4
|
+
public BYOC image `local-agent-full`. Nothing here runs on a normal push.
|
|
5
|
+
|
|
6
|
+
## npm package
|
|
7
|
+
|
|
8
|
+
`.github/workflows/npm-publish.yml` runs on every push to `main` that touches
|
|
9
|
+
`src/**`, `package.json` or `bin/**`, and bumps the **patch** version
|
|
10
|
+
automatically (from the highest of the latest `v*` tag and `package.json`).
|
|
11
|
+
That is right for routine fixes and wrong for a minor release: with
|
|
12
|
+
`package.json` at `1.1.0` the automatic bump would publish `1.1.1`.
|
|
13
|
+
|
|
14
|
+
To publish an exact version (for example `1.1.0`):
|
|
15
|
+
|
|
16
|
+
1. Leave `package.json` as it is. Do **not** bump it in a PR (a push to `main`
|
|
17
|
+
would trigger the automatic patch bump).
|
|
18
|
+
2. Actions → **Publish NPM Package** → *Run workflow* on `main`, input
|
|
19
|
+
`version` = `1.1.0`. The workflow validates `X.Y.Z`, requires it to be
|
|
20
|
+
greater than the highest existing tag and `package.json`, runs
|
|
21
|
+
`npm version 1.1.0`, publishes with provenance, then pushes the bump commit
|
|
22
|
+
and the `v1.1.0` tag.
|
|
23
|
+
3. Left empty, the input keeps the old behaviour (automatic patch bump).
|
|
24
|
+
|
|
25
|
+
Prerequisite (owner of the npm package): Trusted Publisher configured for
|
|
26
|
+
`VISIONHAMMER/deepdebug-local-agent`, workflow `npm-publish.yml` (see the
|
|
27
|
+
header of the workflow). Without it the publish step fails and no version is
|
|
28
|
+
burned.
|
|
29
|
+
|
|
30
|
+
Heads-up: merging any PR that changes `src/**` to `main` starts an automatic
|
|
31
|
+
patch publish (for example `1.0.39`) once the Trusted Publisher exists. To
|
|
32
|
+
release `1.1.0` as the next version, dispatch it before merging further
|
|
33
|
+
`src/**` changes, or merge with `[skip ci]` in the commit message.
|
|
34
|
+
|
|
35
|
+
## BYOC image (`local-agent-full`)
|
|
36
|
+
|
|
37
|
+
Tags pushed by `npm-publish.yml` use `GITHUB_TOKEN` and do not trigger other
|
|
38
|
+
workflows, so after the npm release run Actions → **Build BYOC Agent Image**
|
|
39
|
+
on `main` with `version` = `1.1.0`
|
|
40
|
+
(see `docs/byoc-agent-image.md`). Tick `supply_chain` to also sign the image
|
|
41
|
+
(cosign keyless), attach the SBOM and the provenance, and verify them.
|
|
42
|
+
For `v*` tag and release runs the same job runs when the repository variable
|
|
43
|
+
`BYOC_SUPPLY_CHAIN` is `true`. Default: off.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Signed and allow-listed commands — operator notes
|
|
2
|
+
|
|
3
|
+
The platform (vh-api-gateway, visionhammer-agents) signs every command it sends
|
|
4
|
+
to a local agent with an Ed25519 key, and the agent runs shell commands only
|
|
5
|
+
from an allowlist. Both checks have three modes, `off | warn | enforce`. The
|
|
6
|
+
default is `enforce` (signature: since 1.1.0; exec policy: since 1.0.35), and an
|
|
7
|
+
unrecognised value also means `enforce`. `warn` verifies and logs without
|
|
8
|
+
rejecting; use it only as an explicit, temporary opt-out
|
|
9
|
+
(`LOCAL_AGENT_SIGNATURE_MODE=warn`) for an agent whose platform side does not
|
|
10
|
+
sign yet. Without a listen address the agent also binds `127.0.0.1` unless a
|
|
11
|
+
token (`LOCAL_AGENT_TOKEN` / `AGENT_SHARED_SECRET`) or Cloud Run is present.
|
|
12
|
+
|
|
13
|
+
## Protocol v1
|
|
14
|
+
|
|
15
|
+
HTTP headers on every platform → agent request:
|
|
16
|
+
|
|
17
|
+
| Header | Value |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `X-VH-Key-Id` | id of the signing key, for example `vh-2026-09` |
|
|
20
|
+
| `X-VH-Timestamp` | unix epoch **seconds** |
|
|
21
|
+
| `X-VH-Nonce` | 16–128 chars `[A-Za-z0-9_-]`, unique per request |
|
|
22
|
+
| `X-VH-Tenant-Id` / `X-VH-Workspace-Id` | the tenant and workspace the platform acts for (`""` when unknown) |
|
|
23
|
+
| `X-VH-Signature` | base64 Ed25519 signature over the canonical string |
|
|
24
|
+
|
|
25
|
+
Canonical string (UTF-8, `\n`-joined, no trailing newline):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
VH-AGENT-SIG-V1
|
|
29
|
+
<METHOD upper-case>
|
|
30
|
+
<raw path + "?" + raw query, as sent>
|
|
31
|
+
<hex sha256 of the exact body bytes (sha256("") when empty)>
|
|
32
|
+
<tenantId>
|
|
33
|
+
<workspaceId>
|
|
34
|
+
<timestamp>
|
|
35
|
+
<nonce>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Golden vector (all three implementations test against it): method `post`, path
|
|
39
|
+
`/workspace/exec?x=1`, body `{}`, tenant `t`, workspace `w`, timestamp
|
|
40
|
+
`1790000000`, nonce `abcdefghijklmnop` →
|
|
41
|
+
`VH-AGENT-SIG-V1\nPOST\n/workspace/exec?x=1\n44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\nt\nw\n1790000000\nabcdefghijklmnop`.
|
|
42
|
+
|
|
43
|
+
WebSocket tunnel: the envelope gains `paramsRaw` (the exact JSON the platform
|
|
44
|
+
signed) and `sig: {keyId, timestamp, nonce, tenantId, workspaceId, signature}`.
|
|
45
|
+
Method is `WS`, path is the command name, body is `paramsRaw`. A verified
|
|
46
|
+
envelope runs `JSON.parse(paramsRaw)`; the unsigned `params` copy is used only
|
|
47
|
+
by older agents, or in `warn`/`off` mode when the envelope is not signed.
|
|
48
|
+
|
|
49
|
+
The agent rejects (in `enforce`) with 401 when:
|
|
50
|
+
|
|
51
|
+
* the request has no signature, or the key id is unknown;
|
|
52
|
+
* the signature does not verify;
|
|
53
|
+
* `|now − timestamp| > 60 s`;
|
|
54
|
+
* the nonce was already used (in-memory LRU, TTL 120 s, 100k entries);
|
|
55
|
+
* the tenant does not match the agent's identity (`LOCAL_AGENT_TENANT_ID` →
|
|
56
|
+
`TENANT_ID` → `~/.deepdebug/config.json`). If the signed workspace is
|
|
57
|
+
non-empty, it must match `LOCAL_AGENT_WORKSPACE_ID` / `WORKSPACE_ID` (set
|
|
58
|
+
`LOCAL_AGENT_REQUIRE_WORKSPACE=true` to require it on every call). A shared
|
|
59
|
+
operator agent with no identity accepts any signed tenant.
|
|
60
|
+
|
|
61
|
+
`X-Agent-Token` (`LOCAL_AGENT_TOKEN`) is still checked first, as the second
|
|
62
|
+
factor. `GET /health` and `/metrics` stay public.
|
|
63
|
+
|
|
64
|
+
## Exec allowlist
|
|
65
|
+
|
|
66
|
+
`/workspace/exec`, `/workspace/run`, `/mcp/execute-command` and
|
|
67
|
+
`/setup/run-command` accept a command only when every segment (`&&`, `||`,
|
|
68
|
+
`;`, `|`) belongs to one of these families: read-only git; package-manager
|
|
69
|
+
install, test, build or lint for an ecosystem detected in the workspace (npm,
|
|
70
|
+
yarn, pnpm, mvn/mvnw, gradle/gradlew, pip, poetry, uv, go, cargo, dotnet); test
|
|
71
|
+
runners; linters; file read/write inside the workspace root; or `cd` inside
|
|
72
|
+
the root. `$VAR`, `$(...)`, backticks, subshells, `&`, here-docs, process
|
|
73
|
+
substitution and paths outside the root are refused.
|
|
74
|
+
|
|
75
|
+
Hardening rules:
|
|
76
|
+
|
|
77
|
+
* **Wrappers are validated recursively.** `env`, `time`, `nice`, `timeout`,
|
|
78
|
+
`c8`, `nyc`, `npx`, `npm/pnpm exec`, `poetry run` and `uv run` wrap another
|
|
79
|
+
command, and that command must pass the same policy. `xargs` is refused.
|
|
80
|
+
Interpreter eval (`node -e/-p`, `python -c`, `sh/bash -c`) is never
|
|
81
|
+
allowed. Test runners refuse inline JSON config.
|
|
82
|
+
* **Paths are realpath-resolved.** Every non-flag token that is path-like or
|
|
83
|
+
exists on disk, every `--opt=value`, every redirect target and every
|
|
84
|
+
`cd`/`git -C` directory is resolved with symlinks followed and must stay
|
|
85
|
+
inside the root. An unquoted glob may use wildcards only in its last segment,
|
|
86
|
+
and none of the symlinks it can match may point outside.
|
|
87
|
+
* **Protected files.** A redirect, `tee`, `cp`, `mv`, `rm`, `touch`, `chmod`
|
|
88
|
+
or `sort -o` is refused on these paths: `.git/**`, hook dirs (`.husky`,
|
|
89
|
+
`.githooks`), and build/test config that executes code. That includes
|
|
90
|
+
`package.json`, `.npmrc`, lockfiles, `Makefile`, `pom.xml`, `*.gradle*`,
|
|
91
|
+
`gradle/wrapper`, `setup.py`, `pyproject.toml`, `conftest.py`, and
|
|
92
|
+
`*.config.js` for jest, vitest, eslint and similar tools. Those files change
|
|
93
|
+
only through the signed apply-patch tool.
|
|
94
|
+
* **No write-then-execute.** A command string that writes files may not run a
|
|
95
|
+
package-manager, test or lint tool in a later segment. The source of truth is
|
|
96
|
+
`src/utils/exec-policy.js`; the reason is returned in the 403 body.
|
|
97
|
+
|
|
98
|
+
Two exceptions:
|
|
99
|
+
|
|
100
|
+
1. **Human approval (REQUIRE_HUMAN).** An `X-VH-Approval` header (or
|
|
101
|
+
`approvalToken` in the body) holding a PolicyGate token
|
|
102
|
+
`vhapr1.<keyId>.<b64url payload>.<b64url sig>`. It is signed with the same
|
|
103
|
+
platform key over `VH-HUMAN-APPROVAL-V1\n<keyId>\n<payload segment>`. The
|
|
104
|
+
token is bound to the SHA-256 of the exact command, the tenant and the
|
|
105
|
+
workspace. It lives at most 15 minutes and can be used once.
|
|
106
|
+
2. **Platform-authored tools.** A body with `commandOrigin: "platform"` and a
|
|
107
|
+
known `tool` (`create_branch`, `git_commit`, `git_push`, `close_pr`,
|
|
108
|
+
`delete_branch`, `publish_scope`, `current_branch`). It is honoured only
|
|
109
|
+
when the request signature verified. The model's free-form `run_command`
|
|
110
|
+
never carries it.
|
|
111
|
+
|
|
112
|
+
## Rollout
|
|
113
|
+
|
|
114
|
+
1. **Generate the keypair** on a trusted workstation. Keep the private key
|
|
115
|
+
off disk after upload.
|
|
116
|
+
```sh
|
|
117
|
+
openssl genpkey -algorithm ed25519 -out local-agent-signing-key.pem
|
|
118
|
+
openssl pkey -in local-agent-signing-key.pem -pubout -outform DER | base64 # SPKI, 60 chars
|
|
119
|
+
```
|
|
120
|
+
2. **Store the private key** in Secret Manager as `local-agent-signing-key`
|
|
121
|
+
(PKCS#8 PEM) in `visionhammer`, and grant `secretAccessor` to the gateway
|
|
122
|
+
and agent2 runtime service accounts.
|
|
123
|
+
```sh
|
|
124
|
+
gcloud secrets create local-agent-signing-key --replication-policy=automatic --data-file=local-agent-signing-key.pem
|
|
125
|
+
```
|
|
126
|
+
3. **Mount it on the signers.** On gateway and software-engineer (agent2)
|
|
127
|
+
Cloud Run, set
|
|
128
|
+
`--set-secrets=LOCAL_AGENT_SIGNING_KEY=local-agent-signing-key:latest` and
|
|
129
|
+
`--update-env-vars=LOCAL_AGENT_SIGNING_KEY_ID=vh-2026-09`. Without the key
|
|
130
|
+
they log once and send unsigned requests, as before. Remember
|
|
131
|
+
`--vpc-egress=private-ranges-only` on `run services update`.
|
|
132
|
+
4. **Give the agents the public key:**
|
|
133
|
+
* Image default: add `{"keyId":"vh-2026-09","publicKey":"<SPKI b64>"}` to
|
|
134
|
+
`src/config/trusted-signing-keys.json` and cut a new image.
|
|
135
|
+
* BYOC provisioning: set `LOCAL_AGENT_SIGNING_PUBLIC_KEYS=vh-2026-09=<SPKI b64>`
|
|
136
|
+
on the gateway. `CloudEngineerService` copies it, along with
|
|
137
|
+
`LOCAL_AGENT_SIGNATURE_MODE` and `LOCAL_AGENT_EXEC_POLICY_MODE`, into
|
|
138
|
+
every ACI/Cloud Run agent it provisions.
|
|
139
|
+
* Operator QA agent: set the same env on `deepdebug-local-agent-qa`.
|
|
140
|
+
5. **Soak in `warn`.** Watch the logs for `[agent-sig] WARN` and
|
|
141
|
+
`[exec-policy] WARN`. The logs are rate-limited, one line per reason and
|
|
142
|
+
path per minute. Expect zero signature warnings from updated platform
|
|
143
|
+
callers. Every exec warning is a command that `enforce` would block.
|
|
144
|
+
6. **Flip to enforce** per agent: `LOCAL_AGENT_SIGNATURE_MODE=enforce` once
|
|
145
|
+
the signature warnings are zero. Set the gateway env to `enforce` so new
|
|
146
|
+
BYOC agents start enforced. Roll back by setting `warn` again, with no
|
|
147
|
+
redeploy of the platform.
|
|
148
|
+
|
|
149
|
+
Since 1.0.35 (security review 2026-10-11) the **exec policy defaults to
|
|
150
|
+
`enforce`**: a command outside the allowlist is refused unless it carries a
|
|
151
|
+
PolicyGate approval token or is a signed platform tool command. Set
|
|
152
|
+
`LOCAL_AGENT_EXEC_POLICY_MODE=warn` explicitly on an agent that still needs
|
|
153
|
+
the soak. In the same release the HTTP perimeter stopped defaulting to
|
|
154
|
+
`compat`: `AGENT_SHARED_SECRET` is enforced as `X-Agent-Token`, and an agent
|
|
155
|
+
with no token outside Cloud Run only answers loopback callers (the reverse
|
|
156
|
+
WS tunnel). `LOCAL_AGENT_AUTH=compat` is the explicit, logged opt-out.
|
|
157
|
+
|
|
158
|
+
### Key rotation
|
|
159
|
+
|
|
160
|
+
Add the new public key next to the old one (`kid1=...,kid2=...` / two file
|
|
161
|
+
entries) on every agent. Then switch `LOCAL_AGENT_SIGNING_KEY` and
|
|
162
|
+
`LOCAL_AGENT_SIGNING_KEY_ID` on the signers to the new version. After a day,
|
|
163
|
+
remove the old public key. Approval tokens use the same keys.
|
package/env.example
CHANGED
|
@@ -39,3 +39,33 @@ LOG_LEVEL=info
|
|
|
39
39
|
|
|
40
40
|
# For on-premise DeepDebug server
|
|
41
41
|
# DEEPDEBUG_API_URL=https://deepdebug.internal.company.com
|
|
42
|
+
|
|
43
|
+
# ─── Security (audit 2026-09) ───
|
|
44
|
+
# LOCAL_AGENT_TOKEN: when set, every route except GET /health and /metrics
|
|
45
|
+
# requires the X-Agent-Token header. Set it only once every caller (gateway,
|
|
46
|
+
# agents) sends the header. AGENT_SHARED_SECRET (injected by the gateway on
|
|
47
|
+
# BYOC ACI agents) is enforced the same way when LOCAL_AGENT_TOKEN is not set.
|
|
48
|
+
# With neither (and outside Cloud Run) the agent is loopback-only: it binds
|
|
49
|
+
# 127.0.0.1 and refuses non-loopback callers. LOCAL_AGENT_BIND_HOST overrides.
|
|
50
|
+
# LOCAL_AGENT_TOKEN=
|
|
51
|
+
# Extra browser origins allowed by CORS (comma-separated, exact match).
|
|
52
|
+
# LOCAL_AGENT_CORS_ORIGINS=
|
|
53
|
+
# Re-enable /setup/run-command, /setup/install-tool, /setup/run-auth-command.
|
|
54
|
+
# LOCAL_AGENT_ENABLE_SETUP_COMMANDS=false
|
|
55
|
+
# LOCAL_AGENT_AUTH=loopback -> accept loopback callers only (laptop + WS tunnel)
|
|
56
|
+
# LOCAL_AGENT_AUTH=off -> emergency opt-out (NOT recommended)
|
|
57
|
+
|
|
58
|
+
# ─── Signed + allow-listed commands (docs/signed-commands.md) ───
|
|
59
|
+
# Trusted platform public keys (Ed25519), keyId=base64 (raw 32-byte or SPKI DER).
|
|
60
|
+
# Several entries = rotation window. The image default lives in
|
|
61
|
+
# src/config/trusted-signing-keys.json.
|
|
62
|
+
# LOCAL_AGENT_SIGNING_PUBLIC_KEYS=vh-2026-09=MCowBQYDK2VwAyEA...
|
|
63
|
+
# off | warn | enforce (default) — verify X-VH-Signature on every command.
|
|
64
|
+
# Set warn only as a loud, temporary opt-out for a platform that does not sign yet.
|
|
65
|
+
# LOCAL_AGENT_SIGNATURE_MODE=enforce
|
|
66
|
+
# off | warn | enforce (default) — shell allowlist on exec endpoints.
|
|
67
|
+
# LOCAL_AGENT_EXEC_POLICY_MODE=enforce
|
|
68
|
+
# Identity the signature must match (defaults: TENANT_ID / WORKSPACE_ID).
|
|
69
|
+
# LOCAL_AGENT_TENANT_ID=
|
|
70
|
+
# LOCAL_AGENT_WORKSPACE_ID=
|
|
71
|
+
# LOCAL_AGENT_REQUIRE_WORKSPACE=false
|
package/helm/Chart.yaml
CHANGED
|
@@ -38,14 +38,24 @@ spec:
|
|
|
38
38
|
value: "production"
|
|
39
39
|
- name: PORT
|
|
40
40
|
value: "5055"
|
|
41
|
-
|
|
41
|
+
# Names aligned to what src/server.js actually reads (server.js frozen):
|
|
42
|
+
# GATEWAY_URL (server.js:73), TENANT_ID (:7505), WORKSPACE_ROOT (:637).
|
|
43
|
+
- name: GATEWAY_URL
|
|
42
44
|
value: {{ .Values.config.apiUrl | quote }}
|
|
43
|
-
- name:
|
|
45
|
+
- name: TENANT_ID
|
|
44
46
|
valueFrom:
|
|
45
47
|
secretKeyRef:
|
|
46
48
|
name: {{ include "deepdebug-agent.fullname" . }}-secret
|
|
47
49
|
key: tenant-id
|
|
48
|
-
-
|
|
50
|
+
# Without a token the agent is loopback-only (bind 127.0.0.1) and the
|
|
51
|
+
# kubelet probes would fail; with it, every route but /health and
|
|
52
|
+
# /metrics needs X-Agent-Token.
|
|
53
|
+
- name: LOCAL_AGENT_TOKEN
|
|
54
|
+
valueFrom:
|
|
55
|
+
secretKeyRef:
|
|
56
|
+
name: {{ include "deepdebug-agent.fullname" . }}-secret
|
|
57
|
+
key: agent-token
|
|
58
|
+
- name: WORKSPACE_ROOT
|
|
49
59
|
value: {{ .Values.config.workspacePath | quote }}
|
|
50
60
|
- name: LOG_LEVEL
|
|
51
61
|
value: {{ .Values.config.logLevel | quote }}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{{- if .Values.networkPolicy.enabled }}
|
|
2
|
+
apiVersion: networking.k8s.io/v1
|
|
3
|
+
kind: NetworkPolicy
|
|
4
|
+
metadata:
|
|
5
|
+
name: {{ include "deepdebug-agent.fullname" . }}
|
|
6
|
+
labels:
|
|
7
|
+
{{- include "deepdebug-agent.labels" . | nindent 4 }}
|
|
8
|
+
spec:
|
|
9
|
+
podSelector:
|
|
10
|
+
matchLabels:
|
|
11
|
+
{{- include "deepdebug-agent.selectorLabels" . | nindent 6 }}
|
|
12
|
+
policyTypes:
|
|
13
|
+
- Ingress
|
|
14
|
+
{{- with .Values.networkPolicy.allowFrom }}
|
|
15
|
+
ingress:
|
|
16
|
+
- from:
|
|
17
|
+
{{- toYaml . | nindent 8 }}
|
|
18
|
+
ports:
|
|
19
|
+
- protocol: TCP
|
|
20
|
+
port: 5055
|
|
21
|
+
{{- end }}
|
|
22
|
+
{{- end }}
|
package/helm/values.yaml
CHANGED
|
@@ -27,6 +27,9 @@ replicaCount: 1
|
|
|
27
27
|
# ─────────────────────────────────────────
|
|
28
28
|
config:
|
|
29
29
|
tenantId: "" # Required: Your tenant ID
|
|
30
|
+
# Required: shared secret for X-Agent-Token (LOCAL_AGENT_TOKEN). Pass it with
|
|
31
|
+
# --set config.agentToken=$(openssl rand -hex 32); never commit it.
|
|
32
|
+
agentToken: ""
|
|
30
33
|
apiUrl: "https://api.deepdebug.ai"
|
|
31
34
|
logLevel: "info"
|
|
32
35
|
|
|
@@ -117,20 +120,6 @@ readinessProbe:
|
|
|
117
120
|
timeoutSeconds: 5
|
|
118
121
|
failureThreshold: 3
|
|
119
122
|
|
|
120
|
-
# ─────────────────────────────────────────
|
|
121
|
-
# Network Policies (Enterprise)
|
|
122
|
-
# ─────────────────────────────────────────
|
|
123
|
-
networkPolicy:
|
|
124
|
-
enabled: true
|
|
125
|
-
# Allow egress only to DeepDebug API
|
|
126
|
-
egressRules:
|
|
127
|
-
- to:
|
|
128
|
-
- ipBlock:
|
|
129
|
-
cidr: 0.0.0.0/0
|
|
130
|
-
ports:
|
|
131
|
-
- protocol: TCP
|
|
132
|
-
port: 443
|
|
133
|
-
|
|
134
123
|
# ─────────────────────────────────────────
|
|
135
124
|
# Pod Disruption Budget
|
|
136
125
|
# ─────────────────────────────────────────
|
|
@@ -160,3 +149,13 @@ podAnnotations:
|
|
|
160
149
|
prometheus.io/scrape: "true"
|
|
161
150
|
prometheus.io/port: "5055"
|
|
162
151
|
prometheus.io/path: "/metrics"
|
|
152
|
+
|
|
153
|
+
# ─────────────────────────────────────────
|
|
154
|
+
# Network Policy (security review 2026-10-11, C1)
|
|
155
|
+
# ─────────────────────────────────────────
|
|
156
|
+
# The agent reaches the platform over an OUTBOUND tunnel, so it needs no
|
|
157
|
+
# inbound traffic except kubelet probes. Default: deny all ingress except the
|
|
158
|
+
# sources listed in allowFrom (NetworkPolicy peers, e.g. a podSelector).
|
|
159
|
+
networkPolicy:
|
|
160
|
+
enabled: true
|
|
161
|
+
allowFrom: []
|
package/mcp-server.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ListToolsRequestSchema,
|
|
7
7
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
import path from "path";
|
|
9
|
+
import { createAuthMiddleware } from "./src/utils/agent-auth.js";
|
|
9
10
|
import { spawn } from "child_process";
|
|
10
11
|
import { exists, listRecursive, readFile, writeFile, stat } from "./fs-utils.js";
|
|
11
12
|
import { detectProject, readText } from "./detectors.js";
|
|
@@ -454,6 +455,8 @@ function errorResponse(message) {
|
|
|
454
455
|
export async function startMCPHttpServer(wsManager, port = 5056) {
|
|
455
456
|
const express = (await import("express")).default;
|
|
456
457
|
const app = express();
|
|
458
|
+
// SECURITY: execute_command runs `sh -c`; same auth perimeter as the main API.
|
|
459
|
+
app.use(createAuthMiddleware());
|
|
457
460
|
|
|
458
461
|
const server = createMCPServer(wsManager);
|
|
459
462
|
|
|
@@ -522,7 +525,7 @@ export async function startMCPHttpServer(wsManager, port = 5056) {
|
|
|
522
525
|
}
|
|
523
526
|
});
|
|
524
527
|
|
|
525
|
-
app.listen(port, () => {
|
|
528
|
+
app.listen(port, process.env.MCP_BIND_HOST || "127.0.0.1", () => {
|
|
526
529
|
console.log(`🧠 DeepDebug MCP Server listening on http://localhost:${port}`);
|
|
527
530
|
console.log(` SSE: http://localhost:${port}/sse`);
|
|
528
531
|
console.log(` REST: http://localhost:${port}/tool/{toolName}`);
|