Skip to content

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

Draft
dawsontoth wants to merge 1 commit 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 1 commit 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)

@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. This is a squashed rebase of the same content the prior review checked — re-verified deriveSecretName (bin/deploySetup.ts) still matches the server's deriveGitSecretName/deriveRegistrySecretName/normalizeGitHost exactly, and confirmed the token never reaches a log line in plaintext. One non-blocking suggestion posted inline.

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>
…ial client-side

A guided flow (what harper login is for auth, but for deploy tokens): fetch get_secrets_public_key, source a token (gh auth token / a pasted fine-grained PAT / npm token create), encryptEnvelope it locally into an enc:v1: envelope, set_secret the ciphertext granted to the component, and print the credentials reference. Plaintext never leaves the machine; the cluster decrypts in-memory only at deploy/rollback. A durable token ⇒ re-resolve rollback works. Unifies git-host and npm-registry credentials. bin/deploySetup.ts + a harper deploy setup=true intercept in bin/harper.ts. The client-side secret name mirrors the server's deriveGitSecretName/deriveRegistrySecretName exactly.

Rebased onto main. Relates to #1778, #1797, #1799.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth
dawsontoth force-pushed the claude/deploy-setup-sealed-token branch from 7e18907 to 32df27d Compare July 28, 2026 23:22
Comment thread bin/deploySetup.ts
Comment on lines +32 to +54
function deriveSecretName(component: string, key: string, provider: string): string {
const componentPart = String(component).replace(/[^\w.-]+/g, '_');
if (provider === 'github') {
// mirrors gitCredentialServer.normalizeGitHost() + deriveGitSecretName's `.git.` segment
const hostPart = key
.trim()
.replace(/^[a-z0-9+.-]+:\/\//i, '')
.replace(/^\/\//, '')
.replace(/\/.*$/, '')
.replace(/^[^@]*@/, '')
.toLowerCase()
.replace(/[^\w.-]+/g, '_');
return `deploy.${componentPart}.git.${hostPart}`;
}
const registryPart = key
.trim()
.replace(/^https?:\/\//i, '')
.replace(/^\/\//, '')
.replace(/\/+$/, '')
.toLowerCase()
.replace(/[^\w.-]+/g, '_');
return `deploy.${componentPart}.${registryPart}`;
}

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.

Suggestion (non-blocking): deriveSecretName is pure logic that must exactly mirror the server's deriveGitSecretName/deriveRegistrySecretName/normalizeGitHost (verified matching in this review) for the credential-overwrite guarantee to hold — a future drift on either side would silently store the secret under the wrong name with no error. This repo already unit-tests analogous pure CLI logic (unitTests/bin/cliCredentials.test.js); a small test asserting a few (component, key, provider) → name mappings here would catch that drift early.

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