Skip to content

Commit 6a924cf

Browse files
committed
Add secrets isolation OpenSpec proposal
1 parent 67714e8 commit 6a924cf

5 files changed

Lines changed: 329 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-04
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
## Context
2+
3+
The private gateway now rejects value-bearing deploy secret specs and exposes a value-free contract:
4+
5+
```ts
6+
secrets?: {
7+
require?: string[];
8+
delete?: string[];
9+
}
10+
```
11+
12+
The public repo still has several old-shape entry points: SDK `SecretsSpec.set` / `replace_all`, MCP `deploy` schema, CLI `deploy apply` examples, `apps.bundleDeploy` translation, legacy `bundle_deploy` docs, `SecretSummary.value_hash`, and skills/docs that tell agents a hash can verify secret values. If left unchanged, agents will create manifests that fail with `INVALID_SPEC`, or worse, will keep writing secret values to manifests/repos while believing the deploy layer owns them.
13+
14+
This change is client/agent contract work only. Backend KMS, migrations, Bugsnag redaction, `value_hash` removal at the API, and warning generation are treated as shipped by the private commits. The public repo should consume that contract cleanly.
15+
16+
## Goals / Non-Goals
17+
18+
**Goals:**
19+
20+
- Make the SDK `ReleaseSpec` and all public deploy surfaces value-free for secrets.
21+
- Preserve a smooth coding-agent workflow: set secret values out-of-band, then deploy with `secrets.require`.
22+
- Surface `warnings: WarningEntry[]` consistently enough that agents can branch on `MISSING_REQUIRED_SECRET`.
23+
- Remove `value_hash` from all public types, formatted output, docs, and examples.
24+
- Update legacy compatibility paths so they never put secret values into a `ReleaseSpec`.
25+
- Keep the implementation small and aligned with existing SDK/CLI/MCP shim patterns.
26+
27+
**Non-Goals:**
28+
29+
- No gateway/database/KMS/migration work in this repo.
30+
- No new "get secret value" API.
31+
- No Lambda environment variable redesign.
32+
- No broad new secrets dashboard/browser-management feature. Closed issue #10 is related context, not scope.
33+
- No local manifest validation tool in this change. Open issue #151 remains a follow-up that can build on the new schema.
34+
35+
## Decisions
36+
37+
### D1. `ReleaseSpec.secrets` is declaration-only
38+
39+
Change `SecretsSpec` to:
40+
41+
```ts
42+
export interface SecretsSpec {
43+
require?: string[];
44+
delete?: string[];
45+
}
46+
```
47+
48+
Remove `set` and `replace_all` from SDK types and docs. SDK `validateSpec` should reject obvious old-shape objects locally with `Run402DeployError` code `INVALID_SPEC`, `phase: "validate"`, and `resource: "secrets.set"` or `"secrets.replace_all"` before network work. This gives agents a clean local error instead of an opaque gateway rejection.
49+
50+
Alternatives considered:
51+
52+
- Leave old fields in the type as deprecated optional fields and let the gateway reject them. Rejected because TypeScript would still teach agents the unsafe shape.
53+
- Translate `set` to `require` silently. Rejected for plain `deploy.apply` because the values would still appear in manifests and callers would assume deploy is setting them.
54+
55+
### D2. Secret values move through the secrets namespace, not deploy specs
56+
57+
The canonical two-step workflow is:
58+
59+
1. `r.secrets.set(project, key, value)` or `run402 secrets set <project> <key> <value>`.
60+
2. `r.deploy.apply({ project, secrets: { require: [key] }, ... })`.
61+
62+
Do not add a new deploy helper in the first slice. The two-step primitive flow is already simple, transparent, and available across SDK, CLI, and MCP. The "ultimate DX" move is to make the docs and errors so obvious that agents choose the safe two-step path without inventing value-bearing manifests.
63+
64+
Alternatives considered:
65+
66+
- Add `--set-secret KEY=VALUE` to `deploy apply`. Rejected for v1 because it encourages shell-history leakage and mixes two authorities into one command.
67+
- Require every caller to hand-roll the two-step flow. Accepted for v1 because it keeps authority boundaries clear and uses tools that already exist.
68+
69+
### D3. Legacy compatibility surfaces pre-set or fail safely
70+
71+
`apps.bundleDeploy`, legacy CLI deploy shims, and `bundle_deploy` MCP can still accept older options for non-secret resources, but they must not translate secret values into `ReleaseSpec.secrets.set`.
72+
73+
When a compatibility surface receives secret values and has a project-scoped SDK available, it should:
74+
75+
- call `secrets.set` for each `{key, value}`;
76+
- add those keys to `ReleaseSpec.secrets.require`;
77+
- proceed with deploy;
78+
- surface that secret writes happened before deploy and are not part of release rollback semantics.
79+
80+
If the surface cannot safely pre-set secrets, it should return a structured/actionable error telling the agent to call `set_secret` or `run402 secrets set` first, then deploy with `secrets.require`.
81+
82+
Alternatives considered:
83+
84+
- Remove all legacy secret options immediately. Rejected because a compatibility shim can preserve most agent workflows while still keeping the deploy spec value-free.
85+
- Try to emulate old `replace_all` semantics. Rejected because backend deploy specs no longer carry values and exact replacement semantics are not representable without a separate admin operation.
86+
87+
### D4. `WarningEntry` is typed and propagated through apply
88+
89+
Add:
90+
91+
```ts
92+
export interface WarningEntry {
93+
code: string;
94+
severity: "info" | "warn" | "high";
95+
message: string;
96+
affected: string[];
97+
requires_confirmation: boolean;
98+
confidence?: "heuristic";
99+
details?: Record<string, unknown>;
100+
}
101+
```
102+
103+
`PlanResponse` SHALL include `warnings: WarningEntry[]`. `DeployResult` SHOULD include the plan warnings as `warnings: WarningEntry[]` so one-shot `deploy.apply` callers do not have to drop to `plan` to inspect non-fatal problems. `DeployEvent` should add a `plan.warnings` event when the array is non-empty; CLI event stderr and MCP markdown should display the codes and affected keys.
104+
105+
For `MISSING_REQUIRED_SECRET`, agents can branch on either `warning.code === "MISSING_REQUIRED_SECRET"` or `warning.details?.missing_keys`.
106+
107+
Alternatives considered:
108+
109+
- Keep warnings only on `plan()`. Rejected because `apply()` is the recommended agent primitive.
110+
- Use `string[]`. Rejected because agents need stable fields.
111+
112+
### D5. `value_hash` disappears everywhere
113+
114+
Remove `value_hash` from SDK `SecretSummary`, SDK tests, CLI/MCP formatted output, docs, and skills. `list_secrets` should show only keys and timestamp fields if the gateway returns them; if timestamps are absent in current test fixtures, key-only output is enough.
115+
116+
Alternatives considered:
117+
118+
- Keep `value_hash?: string` as optional for older gateways. Rejected because the goal is to stop teaching value-derived verification. Unknown extra runtime fields can be ignored without typing them.
119+
120+
### D6. Docs are part of the contract
121+
122+
Use `documentation.md` as the checklist. Every agent-facing doc surface must teach:
123+
124+
- never put secret values in deploy manifests;
125+
- use `set_secret` / `run402 secrets set` / `r.secrets.set` to write values;
126+
- use `secrets.require[]` to assert deploy-time dependencies;
127+
- use `secrets.delete[]` to remove keys at activation;
128+
- `list_secrets` is keys-only and cannot verify values by hash;
129+
- secret values still appear as function environment variables at runtime, so do not over-promise total runtime secrecy.
130+
131+
Related issues should be referenced in the implementation notes, especially #151 for future local manifest validation and #198 for help/doc drift.
132+
133+
## Risks / Trade-offs
134+
135+
[Risk: compatibility shim writes secrets before a deploy that later fails] -> Mitigation: document non-atomicity, return clear output, and keep raw `deploy.apply` declaration-only.
136+
137+
[Risk: agents miss warnings because they only call `deploy.apply`] -> Mitigation: carry `warnings` onto `DeployResult` and emit a `plan.warnings` event.
138+
139+
[Risk: docs still contain old value-bearing examples] -> Mitigation: add targeted `rg`-based tests or sync assertions for `secrets.set`, `replace_all`, and `value_hash` in public docs.
140+
141+
[Risk: TypeScript accepts old runtime JSON through `as any`] -> Mitigation: SDK `validateSpec` rejects old keys before uploads or plan calls.
142+
143+
[Risk: old gateway fixtures still include `value_hash`] -> Mitigation: update tests to prove the client ignores absent hashes and docs never rely on them.
144+
145+
## Migration Plan
146+
147+
1. Update SDK types/validation/warning propagation first; tests should fail on old secret shape.
148+
2. Update compatibility shims and CLI/MCP schemas to use `require` / `delete`.
149+
3. Remove `value_hash` formatting and tests.
150+
4. Update docs, skills, help snapshots, and sync tests.
151+
5. Run build, focused SDK/MCP tests, CLI e2e/help tests, sync tests, skill tests, and a final `rg` scan for old contract strings.
152+
153+
Rollback is a source rollback only. The public client must match the deployed gateway; if the backend contract is live, reintroducing old fields is not a safe rollback.
154+
155+
## Open Questions
156+
157+
- Should a future local manifest-validation tool from #151 also validate that every `secrets.require[]` key exists locally/remotely before deploy? This change only updates the live deploy client contract.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Why
2+
3+
Private gateway commits `01929adf`, `59199795`, `9855a977`, `31b4848b`, and `b393d752` made deploy specs value-free for secrets, encrypted the canonical backend store, removed value-derived secret signals, and added structured deploy warnings. The public SDK, CLI, MCP tools, OpenClaw surface, and docs still teach the old `secrets.set` / `secrets.replace_all` shape and `value_hash` listing, which would lead coding agents to author manifests the gateway now rejects and to over-trust a value-derived hash that no longer exists.
4+
5+
The public repo needs a KISS contract update: secret values are set out-of-band through the secrets API, deploy specs only declare required or deleted keys, and warnings are structured enough for agents to recover without guesswork.
6+
7+
## What Changes
8+
9+
- **BREAKING**: Update SDK `ReleaseSpec.secrets` from value-bearing `set` / `replace_all` to value-free `require?: string[]` and `delete?: string[]`.
10+
- **BREAKING**: Remove `SecretSummary.value_hash` from SDK types, MCP `list_secrets` output, CLI help/docs, skills, and agent docs.
11+
- Add a shared SDK `WarningEntry` type and wire `warnings: WarningEntry[]` through deploy plan/apply result surfaces so agents can detect `MISSING_REQUIRED_SECRET` and similar non-fatal plan notes.
12+
- Update SDK normalization, validation, tests, scoped-client wrappers, and CI deploy preflight rules for the new secret shape.
13+
- Update CLI `deploy apply` manifest examples and parsing guidance so manifests use `secrets.require` and `secrets.delete`; keep secret values in `run402 secrets set`, not JSON manifests.
14+
- Update MCP `deploy` schema and tool descriptions so agents call `set_secret` first, then `deploy` with `secrets.require`.
15+
- Update legacy compatibility shims (`apps.bundleDeploy`, legacy CLI/MCP bundle surfaces) to stop putting values into `ReleaseSpec`; either pre-set secrets via the secrets API and convert keys to `require`, or fail with a structured/actionable error if a safe project-scoped pre-set is not possible.
16+
- Update README, SDK/CLI docs, `llms*.txt`, skills, OpenClaw docs, sync tests, and help snapshots using `documentation.md` as the checklist.
17+
- Add focused tests for the new type contract, warning propagation, docs/help drift, MCP formatting, CLI manifest examples, and old-shape rejection.
18+
19+
## Capabilities
20+
21+
### New Capabilities
22+
23+
- `secrets-isolation-client-contract`: Public SDK/CLI/MCP/OpenClaw/docs contract for value-free deploy secret declarations, structured deploy warnings, and write-only secret listing with no value-derived hash.
24+
25+
### Modified Capabilities
26+
27+
- None.
28+
29+
## Impact
30+
31+
- **SDK**: `sdk/src/namespaces/deploy.types.ts`, `deploy.ts`, `apps.ts`, `secrets.ts`, scoped exports, root and `/node` exports, CI spec restrictions, and related tests.
32+
- **CLI/OpenClaw**: `cli/lib/deploy-v2.mjs`, `cli/lib/deploy.mjs`, `cli/lib/secrets.mjs`, help snapshots, e2e tests, `openclaw/SKILL.md`, and OpenClaw command guidance.
33+
- **MCP**: `src/tools/deploy.ts`, `src/tools/list-secrets.ts`, `src/tools/bundle-deploy.ts`, tool descriptions in `src/index.ts`, and tool tests.
34+
- **Docs**: `README.md`, `sdk/README.md`, `sdk/llms-sdk.txt`, `cli/README.md`, `cli/llms-cli.txt`, `llms.txt`, `SKILL.md`, `openclaw/SKILL.md`, and any doc surfaces flagged by `documentation.md`.
35+
- **Related feature requests identified**: no exact open "secrets isolation" feature request exists. Related issues are [#151](https://github.com/kychee-com/run402/issues/151) (open enhancement: local manifest validation loop for agents), [#225](https://github.com/kychee-com/run402/issues/225) (open enhancement: deploy auto-retry on safe-to-retry errors, adjacent agent deploy DX), and [#10](https://github.com/kychee-com/run402/issues/10) (closed enhancement: browser/admin secret management). Also relevant but not feature-request-labeled: [#198](https://github.com/kychee-com/run402/issues/198) on outdated deploy help showing value-bearing secret examples.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Deploy secret declarations are value-free
4+
5+
The public deploy contract SHALL represent secrets only as declared dependencies and declared deletions. `ReleaseSpec.secrets` SHALL accept `require?: string[]` and `delete?: string[]`; it SHALL NOT accept value-bearing `set` or `replace_all` fields in SDK types, CLI/MCP examples, or agent documentation.
6+
7+
#### Scenario: Deploy spec requires existing secrets
8+
9+
- **WHEN** a caller builds a deploy spec with `secrets.require: ["OPENAI_API_KEY"]`
10+
- **THEN** the SDK, CLI, and MCP deploy surfaces SHALL accept the declaration without requiring or carrying the secret value
11+
12+
#### Scenario: Deploy spec deletes secrets
13+
14+
- **WHEN** a caller builds a deploy spec with `secrets.delete: ["OLD_API_KEY"]`
15+
- **THEN** the SDK, CLI, and MCP deploy surfaces SHALL forward the key-only delete declaration to the gateway
16+
17+
#### Scenario: Old value-bearing deploy shape is rejected
18+
19+
- **WHEN** a caller provides `secrets.set` or `secrets.replace_all` to the SDK deploy primitive
20+
- **THEN** the SDK SHALL fail before content upload or plan creation with a structured `INVALID_SPEC` deploy error that names the rejected field
21+
22+
### Requirement: Secret values are written out-of-band
23+
24+
The public client surfaces SHALL teach and support the workflow where secret values are written through the secrets namespace before a deploy spec declares those keys in `secrets.require`.
25+
26+
#### Scenario: Agent sets a secret before requiring it
27+
28+
- **WHEN** an agent needs `OPENAI_API_KEY` for a function deploy
29+
- **THEN** the docs and tools SHALL direct the agent to call `set_secret`, `run402 secrets set`, or `r.secrets.set` before deploying with `secrets.require: ["OPENAI_API_KEY"]`
30+
31+
#### Scenario: Compatibility shim receives legacy secret values
32+
33+
- **WHEN** a legacy compatibility surface receives secret values from an older bundle-deploy shape
34+
- **THEN** it SHALL NOT place those values in `ReleaseSpec`; it SHALL either pre-set them through the secrets API and require the keys, or fail with an actionable error telling the agent to set secrets first
35+
36+
### Requirement: Secret listing is key-only
37+
38+
The public secret-listing contract SHALL NOT expose or document `value_hash` or any other value-derived verification signal. SDK types, CLI output, MCP output, and docs SHALL treat secret values as write-only.
39+
40+
#### Scenario: SDK lists secrets
41+
42+
- **WHEN** `r.secrets.list(projectId)` resolves
43+
- **THEN** the typed secret summaries SHALL include the secret key and non-sensitive metadata only, with no `value_hash` property
44+
45+
#### Scenario: MCP formats listed secrets
46+
47+
- **WHEN** the `list_secrets` MCP tool returns one or more secrets
48+
- **THEN** the markdown table SHALL NOT contain a hash column or text instructing agents to verify values by hash
49+
50+
### Requirement: Deploy warnings are structured
51+
52+
The SDK SHALL define and export `WarningEntry`, and deploy plan/apply surfaces SHALL preserve gateway warnings as structured arrays suitable for coding-agent branching.
53+
54+
#### Scenario: Plan response includes missing required secret warning
55+
56+
- **WHEN** the gateway returns a plan response with `warnings` containing `code: "MISSING_REQUIRED_SECRET"`
57+
- **THEN** the SDK `PlanResponse` type SHALL expose the warning with `severity`, `message`, `affected`, `requires_confirmation`, and `details`
58+
59+
#### Scenario: Apply caller receives plan warnings
60+
61+
- **WHEN** `r.deploy.apply(spec)` observes non-empty plan warnings
62+
- **THEN** the SDK SHALL surface those warnings in deploy progress events and in the final `DeployResult` when the deploy reaches ready
63+
64+
#### Scenario: CLI and MCP display warning codes
65+
66+
- **WHEN** deploy warnings are present
67+
- **THEN** CLI and MCP deploy outputs SHALL include the warning `code`, `message`, and affected keys so agents can decide the next action
68+
69+
### Requirement: Agent-facing docs describe the new contract
70+
71+
Every public agent-facing documentation surface that mentions deploy secrets SHALL describe value-free deploy declarations and out-of-band secret setting, and SHALL avoid examples that place secret values in manifests.
72+
73+
#### Scenario: Documentation scan for removed fields
74+
75+
- **WHEN** the repository's docs and skill files are checked after this change
76+
- **THEN** agent-facing docs SHALL NOT present `secrets.set`, `secrets.replace_all`, or `value_hash` as supported deploy/listing behavior
77+
78+
#### Scenario: Documentation links related agent DX follow-ups
79+
80+
- **WHEN** implementation notes mention related public issues
81+
- **THEN** they SHALL identify #151 as the local manifest-validation follow-up and #198 as prior deploy-help drift context

0 commit comments

Comments
 (0)