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 1 commit into
harper deploy setup=true — seal a durable deploy credential client-side#1851Conversation
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. This is a squashed rebase of the same content the prior review checked — re-verified |
…<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>
7e18907 to
32df27d
Compare
| 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}`; | ||
| } |
There was a problem hiding this comment.
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.
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)