feat(cli): harper deploy setup=true — seal a durable deploy credential client-side#1851
feat(cli): harper deploy setup=true — seal a durable deploy credential client-side#1851dawsontoth wants to merge 2 commits into
harper deploy setup=true — seal a durable deploy credential client-side#1851Conversation
…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>
There was a problem hiding this comment.
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'.
| if (cliApiOp.operation === 'deploy_component' && cliApiOp.setup) { | ||
| const { deploySetup } = require('./deploySetup'); | ||
| await deploySetup(cliApiOp); | ||
| return; | ||
| } |
There was a problem hiding this comment.
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.
| 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
- 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.
There was a problem hiding this comment.
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
|
Reviewed; no blockers found. The prior blocker ( |
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>
…<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>
harper deploy setup=true— a guided, client-side credential-provisioning flow (whatharper loginis for auth, but for deploy tokens):get_secrets_public_key→ the cluster's public key + fingerprint.gh auth token, a pasted fine-grained PAT (Contents: Read-only), ornpm token createfor a private registry.encryptEnvelope(...)→ seal it locally into anenc:v1:envelope (pure client-side crypto; the plaintext never leaves the machine).set_secret { envelope, grants:[component] }→ store only ciphertext; the cluster decrypts it in-memory only at deploy/rollback time.credentialsreference 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+ aharper deploy setup=trueintercept inbin/harper.ts. Builds on thecredentialsarray (#1797), the git-host credential (#1799), and thehdb_secretclient-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)