KeeperHub / KeeperHub/keeperhub
Workflows have no configuration layer: no org-scoped values, and no state that survives a run
- Dominant language
- TypeScript
- Stars
- 24
- Forks
- 93
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 253
Description
Tracking issue. The work is broken into the two issues checklisted below; discussion of the overall direction belongs here, implementation discussion belongs on the individual issues.
## Reason: what cannot be done today
A workflow has no configuration layer and no memory. Both gaps have the same practical effect: anything a workflow needs to know that is not a node output has to be hardcoded into the node config, copied into every workflow that needs it, and edited by hand in each one when it changes.
**No organization-scoped value.** Template references are node-scoped by construction. The grammar is `{{@nodeId:Label.field}}` (`lib/workflow/template-ref.ts`) and the runtime resolver takes a node id and resolves a path against that node's recorded outputs (`resolveTemplateToRawValueChecked`, `lib/workflow/executor/executor.workflow.ts:1452`); its failure reasons are `no-node`, `no-data`, `no-path` (`lib/workflow/executor/template-resolution.ts`). A trigger is itself a node, so trigger fields resolve the same way. There is no other namespace a template can address. The editor's autocomplete offers exactly the same surface (`getCommonFields`, `lib/workflow/editor/template-helpers.ts:309`). There is no organization variable or secret table anywhere in the schema (`lib/db/schema.ts`, `lib/db/schema-extensions.ts`). The only place a credential can be stored is an `integrations` row (`lib/db/schema.ts:562`), which is a typed integration for a specific provider - it cannot hold an arbitrary API key, a contract address that varies per environment, or a threshold shared across twenty workflows.
There is also no fallback: the Code node cannot read a value out of the process environment. The sandbox child is spawned with an env built from a six-entry allowlist - `NODE_ENV`, `NODE_EXTRA_CA_CERTS`, `PATH`, `TZ`, `LANG`, `LC_ALL` - carrying the comment "Do NOT add application secrets here" (`CHILD_ENV_ALLOWLIST` / `buildChildEnv`, `sandbox/src/run-code.ts:29-49`). That is the correct behaviour for a multi-tenant sandbox, and it leaves no supported alternative.
**No state that survives a run.** There is no workflow-scoped key-value store. A monitor that wants a cursor - "the last block I scanned", "the transactions I have already alerted on" - has two options today: re-scan a rolling window on every run, or provision an external database and reach it through the Database Query node. Provisioning a Postgres instance to persist one key is disproportionate, and the rolling-window workaround has a visible failure mode: a resolved alert can reopen when the same underlying transaction is still inside the window on the next pass.
## Scope
Two pieces, separately shippable, separately useful:
1. Organization-scoped variables and secrets, referenced from workflow templates
2. Workflow-scoped key-value state that survives between runs
**Not in scope:** replacing or extending the `integrations` model; per-user (as opposed to per-organization) values; a general-purpose database, which is what the Database Query node already is.
## Design questions that span both
These should be settled here before either sub-issue is implemented, because they determine the storage and read paths for both.
- **Read scope.** Who can read a value back versus only reference it. `components/secret-field.tsx` already establishes the pattern for stored credentials - the field renders a mask and the value never reaches the client. Anything called a secret should hold to that.
- **Exposure through execution logs.** `workflow_execution_logs` stores per-step `input` and `output` as jsonb (`lib/db/schema.ts:839`). There is already a redaction path on the output side - `output` goes through `redactSensitiveData()` for display while `output_raw` keeps the unredacted value for cross-process resume - and no equivalent on the input side. A referenced secret must not become readable by being interpolated into a node's recorded input.
- **Whether the Code node can reference secrets at all.** The env scrub was deliberate. Re-exposing values inside the sandbox by another route needs an explicit decision, not an implementation detail buried in a pull request.
- **Precedence and shadowing.** If both an organization variable and a workflow state key exist under the same name, what wins, and is that visible to the person editing the workflow.
## Sub-issues
- [ ] #2287 - Workflows cannot reference an organization-scoped variable or secret
- [ ] #2288 - Nothing a workflow computes survives the run: no workflow-scoped key-value state
---
Tracking: [KEEP-44](https://linear.app/keeperhubapp/issue/KEEP-44) (internal tracker, not publicly accessible)
Contributor guide
Research direction
Start with sub-issues #2287 and #2288, then read lib/workflow/template-ref.ts, lib/workflow/executor/executor.workflow.ts, lib/workflow/executor/template-resolution.ts, and lib/db/schema.ts. Review components/secret-field.tsx and sandbox/src/run-code.ts for the existing credential and environment boundaries. Done means the cross-cutting design questions are settled and both sub-issues have independently actionable scopes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, databases, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100