Skip to content

feat(cli): harper deploy setup=true — seal a durable deploy credential client-side#1851

Draft
dawsontoth wants to merge 2 commits into
mainfrom
claude/deploy-setup-sealed-token
Draft

feat(cli): harper deploy setup=true — seal a durable deploy credential client-side#1851
dawsontoth wants to merge 2 commits into
mainfrom
claude/deploy-setup-sealed-token

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Draft / prototype for #1778. Part of the create-harper deploy-by-reference effort.

harper deploy setup=true — a guided, client-side credential-provisioning flow (what harper login is for auth, but for deploy tokens):

  1. get_secrets_public_key → the cluster's public key + fingerprint.
  2. Source a token interactively — gh auth token, a pasted fine-grained PAT (Contents: Read-only), or npm token create for a private registry.
  3. encryptEnvelope(...) → seal it locally into an enc:v1: envelope (pure client-side crypto; the plaintext never leaves the machine).
  4. set_secret { envelope, grants:[component] } → store only ciphertext; the cluster decrypts it in-memory only at deploy/rollback time.
  5. Print the credentials reference the deploy uses.

Because the token is durable (unlike a CI GITHUB_TOKEN, revoked at job end), a re-resolve rollback works for as long as it's valid. One flow unifies git-host ({host, …}) and npm-registry ({registry, …}) credentials.

Where: new bin/deploySetup.ts + a harper deploy setup=true intercept in bin/harper.ts. Builds on the credentials array (#1797), the git-host credential (#1799), and the hdb_secret client-side envelope (#1717); custody ships on by default (harper-pro#560). Parse + oxlint + prettier clean.

Refs #1778, #1797, #1799.

Related — deploy-by-reference effort

Prototype PRs: #1850 (deploy by_ref) · #1851 (deploy setup) · HarperFast/harper-pro#594 (add_ssh_key generate) · HarperFast/create-harper#118 (create-harper scaffold)

Tracking issues: #1777 · #1778 · HarperFast/harper-pro#570 · HarperFast/create-harper#117 · #641 (rollback)

…ial client-side

Interactive command that provisions the credential the cluster uses to fetch a private deploy source (private GitHub repo, or private npm registry): (1) fetch the cluster's public secrets key (get_secrets_public_key); (2) source a token (gh auth token, or a pasted fine-grained PAT / npm token create); (3) envelope-encrypt it LOCALLY with encryptEnvelope — only ciphertext leaves the machine; (4) store via set_secret {envelope}, granted to the component; (5) print the credentials reference the deploy uses.

Because the token is sealed client-side, the plaintext never touches the request body, the operations log, replication, or the server heap — the cluster decrypts it only in-memory at deploy/rollback time. And because it's a durable token (unlike a CI GITHUB_TOKEN that dies at job end), a re-resolve rollback works for the token's lifetime. Wired as `harper deploy setup=true`, intercepted in bin/harper.ts before the deploy dispatch.

Prototype for the create-harper deploy-by-reference work; concretizes #1778. Relates to #1777, #1799, #641.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an interactive client-side setup flow (deploySetup) to securely provision and seal credentials for private GitHub repositories or npm registries using the cluster's public key. It also updates the main CLI entry point to intercept and route the setup command. The review feedback highlights several critical issues: a mismatch in the secret naming convention for git hosts compared to the server-side implementation, a missing argument in the secret name derivation call, a lack of validation for unsupported providers, a potential runtime TypeError if the token is not a string, and the need for a more robust truthiness check on the setup flag to handle stringified booleans like 'false' or '0'.

Comment thread bin/deploySetup.ts Outdated
Comment thread bin/deploySetup.ts Outdated
Comment thread bin/deploySetup.ts
Comment thread bin/deploySetup.ts Outdated
Comment thread bin/harper.ts
Comment on lines +158 to +162
if (cliApiOp.operation === 'deploy_component' && cliApiOp.setup) {
const { deploySetup } = require('./deploySetup');
await deploySetup(cliApiOp);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When CLI arguments are parsed, boolean-like flags can sometimes be represented as strings (e.g., "false" or "0"). Relying on a simple truthiness check on cliApiOp.setup can cause the setup flow to run even when the user explicitly passes setup=false. We should use a more robust check to ensure setup is not a falsy string.

Suggested change
if (cliApiOp.operation === 'deploy_component' && cliApiOp.setup) {
const { deploySetup } = require('./deploySetup');
await deploySetup(cliApiOp);
return;
}
if (cliApiOp.operation === 'deploy_component' && cliApiOp.setup && cliApiOp.setup !== 'false' && cliApiOp.setup !== '0') {
const { deploySetup } = require('./deploySetup');
await deploySetup(cliApiOp);
return;
}
References
  1. When evaluating boolean-like environment variables in JavaScript or TypeScript, avoid relying on simple truthiness checks because environment variables are always strings, meaning values like 'false' or '0' are truthy. Instead, use explicit checks to treat '0' and 'false' as falsy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as-is: buildRequest JSON-parses each key=value arg, so setup=false becomes boolean false and setup=0 becomes 0 (both falsy) before this check runs — the stringy 'false'/'0' case can't reach here. The truthiness check is already correct.

🤖 Addressed by Claude Code

Comment thread bin/deploySetup.ts Outdated
Comment thread bin/deploySetup.ts Outdated
Comment thread bin/deploySetup.ts
@claude

claude Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found. The prior blocker (deriveSecretName mismatched the server's git-host secret naming) and both non-blocking suggestions (non-string token= guard, provider validation) are all fixed in 7e18907 — verified deriveSecretName now matches deriveGitSecretName/deriveRegistrySecretName/normalizeGitHost in components/secretOperations.ts / components/gitCredentialServer.ts exactly.

deriveSecretName now mirrors the server's deriveGitSecretName/deriveRegistrySecretName exactly: git hosts seal under deploy.<component>.git.<host> (normalizeGitHost handling), registries under deploy.<component>.<registry> — so a `deploy setup` seal and a literal-token deploy hit the same hdb_secret row. Also: validate provider is github|npm (reject others loudly), and guard token.trim() against a non-string token= arg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dawsontoth added a commit that referenced this pull request Jul 17, 2026
…<host>)

The credential= reference must resolve to the same hdb_secret row that `harper deploy setup=true` seals (and a literal-token deploy would auto-name). Renamed the derivation to deriveGitSecretName, mirroring the server's exactly (the .git. segment + normalizeGitHost). Keeps by_ref (#1850) and deploy setup (#1851) consistent with core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant