Skip to content

feat: env-updater plugin for cross-platform env sync#756

Open
threebeats wants to merge 2 commits into
profullstack:masterfrom
threebeats:feat/env-updater-plugin
Open

feat: env-updater plugin for cross-platform env sync#756
threebeats wants to merge 2 commits into
profullstack:masterfrom
threebeats:feat/env-updater-plugin

Conversation

@threebeats

Copy link
Copy Markdown
Contributor

Closes #710

Adds @profullstack/secrets-env-updater — syncs env vars across .env, Doppler, Railway, and GitHub Secrets with one command.

Usage: sh1pt secret push --provider env-updater

Adds a new secrets provider that syncs environment variables across:
- Local .env files
- Doppler (via Doppler CLI)
- Railway (via Railway CLI)
- GitHub Secrets (via gh CLI)

The provider extends the existing secrets architecture with a unified
push/pull interface that updates all configured targets at once.

Closes profullstack#710

Usage: sh1pt secret push --provider env-updater
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces @profullstack/secrets-env-updater, a new sh1pt plugin that syncs env vars across local .env files, Doppler, Railway, and GitHub Secrets in a single push call. Prior review concerns around shell injection, temp-file leakage, and .env truncation have all been addressed using spawnSync with explicit args arrays, stdin-based GitHub secret delivery, and merge-safe file writes.

  • Core plugin (index.ts): Implements connect, pull, and push against four targets; correctly uses spawnSync throughout to avoid shell interpretation.
  • Hardcoded org default: githubOwner silently falls back to 'profullstack' when omitted from config, unlike githubRepo which throws — any user who sets only githubRepo will push secrets to the wrong organization without any warning.
  • Package metadata: main and types still point to the TypeScript source (previously flagged); no build script or devDependencies for TypeScript are present.

Confidence Score: 3/5

Not safe to merge as-is: the hardcoded githubOwner fallback to profullstack will silently push secrets to the wrong GitHub organization for any user who omits that config field.

The githubOwner field defaults to the package author's GitHub org name instead of throwing like githubRepo does. A user who configures only githubRepo: my-app will push all their secrets to profullstack/my-app without any error or warning. The main and types fields in package.json pointing to TypeScript source remain unaddressed.

packages/secrets/env-updater/src/index.ts (line 109 githubOwner default) and packages/secrets/env-updater/package.json (main and types fields).

Important Files Changed

Filename Overview
packages/secrets/env-updater/src/index.ts Core plugin implementation. Shell-injection and temp-file issues from prior review have been addressed (spawnSync + stdin). New issue: githubOwner defaults to 'profullstack', silently misdirecting secrets for any user who omits the field.
packages/secrets/env-updater/src/index.test.ts Minimal smoke tests verifying provider shape only; no behavioral coverage for push/pull/connect or edge cases like missing config fields.
packages/secrets/env-updater/package.json main/types point to the TypeScript source rather than compiled output; no build/test scripts or devDependencies defined (previously flagged).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[sh1pt secret push\n--provider env-updater] --> B[push handler]
    B --> C[Build secretMap\nfrom SecretRef array]
    C --> D[writeEnvFile\nmerge into local .env]
    D --> E{dopplerProject\nconfigured?}
    E -- Yes --> F[updateDoppler\nspawnSync doppler secrets set]
    E -- No --> G{railwayService\nconfigured?}
    F --> G
    G -- Yes --> H[updateRailway\nspawnSync railway variables set]
    G -- No --> I{githubRepo\nconfigured?}
    H --> I
    I -- Yes --> J[updateGitHubSecrets\nspawnSync gh secret set\nvalue via stdin]
    I -- No --> K[Return results summary]
    J --> K
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[sh1pt secret push\n--provider env-updater] --> B[push handler]
    B --> C[Build secretMap\nfrom SecretRef array]
    C --> D[writeEnvFile\nmerge into local .env]
    D --> E{dopplerProject\nconfigured?}
    E -- Yes --> F[updateDoppler\nspawnSync doppler secrets set]
    E -- No --> G{railwayService\nconfigured?}
    F --> G
    G -- Yes --> H[updateRailway\nspawnSync railway variables set]
    G -- No --> I{githubRepo\nconfigured?}
    H --> I
    I -- Yes --> J[updateGitHubSecrets\nspawnSync gh secret set\nvalue via stdin]
    I -- No --> K[Return results summary]
    J --> K
Loading

Reviews (2): Last reviewed commit: "fix: address security audit findings" | Re-trigger Greptile

Comment thread packages/secrets/env-updater/src/index.ts Outdated
Comment thread packages/secrets/env-updater/src/index.ts Outdated
Comment thread packages/secrets/env-updater/src/index.ts Outdated
Comment on lines +44 to +47
async function writeEnvFile(file: string, secrets: Record<string, string>): Promise<void> {
const lines = Object.entries(secrets).map(([key, value]) => `${key}=${value}`);
await writeFile(file, lines.join('\n') + '\n', 'utf8');
}

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.

P1 Unquoted values in writeEnvFile break roundtrip for values with spaces or special characters

readEnvFile strips leading/trailing quotes from values (line 35), but writeEnvFile writes values bare: KEY=hello world. Tools that source the file as shell — e.g., source .env — will misparse unquoted values with spaces, # comments, or embedded = characters. Values should be written with double-quote wrapping, with any embedded double quotes escaped, to preserve round-trip fidelity.

Comment on lines +6 to +7
"main": "./src/index.ts",
"types": "./src/index.ts",

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.

P2 main and types point to TypeScript source, not compiled output

"main": "./src/index.ts" will fail under standard Node.js module resolution since Node cannot import .ts files natively. The types field should also point to a generated .d.ts declaration file, not the source. There are also no scripts (e.g., build, test) and no devDependencies for typescript or vitest, so there is no defined path to produce the compiled artefacts the main/types fields should reference.

- Replace execSync with spawnSync + args arrays (fixes command injection)
- Pass GitHub secrets via stdin instead of temp files (fixes secret leak)
- Merge into existing .env instead of overwriting (fixes destructive overwrite)
@threebeats

Copy link
Copy Markdown
Contributor Author

✅ Security audit findings addressed:

  1. Command injection — Replaced execSync with spawnSync + args arrays for all platform updaters. No shell interpolation.

  2. Secret file leak — GitHub secrets now passed via spawnSync stdin option instead of temp files. No plaintext secrets written to disk.

  3. Destructive .env overwritewriteEnvFile now merges new keys into the existing file instead of replacing it entirely. Existing variables not in the push payload are preserved.

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.

add support for an environment updater plugin

1 participant