KeeperHub / KeeperHub/keeperhub

Workflows have no configuration layer: no org-scoped values, and no state that survives a run

Open
#2,293 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.