Skip to content

fix: protect Promptfoo auth environment - #972

Open
mldangelo-oai wants to merge 92 commits into
mdangelo/codex/sec-env-startup-injectionfrom
mdangelo/codex/sec-auth-host-boundary
Open

fix: protect Promptfoo auth environment#972
mldangelo-oai wants to merge 92 commits into
mdangelo/codex/sec-env-startup-injectionfrom
mdangelo/codex/sec-auth-host-boundary

Conversation

@mldangelo-oai

@mldangelo-oai mldangelo-oai commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • defer repository env/auth handling until an evaluation is actually relevant, while preserving trusted workflow authentication and ordinary application variables
  • preflight config-selected commandLineOptions.envPath in YAML/JSON, including bounded local $ref chains, fragments, nested configs, and in-workspace symlinks; fail closed for executable/dynamic/ambiguous configs, unsafe paths, and resource-amplification cases
  • make changed env/ref/inherited file dependencies and implicit Promptfoo configs evaluation-relevant, including deletions and symlink targets, so a relevant change cannot bypass preflight
  • reject the expanded credential-routing/action-control set, mask provider credentials loaded through the late config path, and regenerate the shipped dist/ bundle from the trusted check-dist artifact

Security impact

Fixes Codex Security finding csf_28f8d46cff789d7927863877 / occurrence occ_4d36979898e538bc09fb79fd.

A contributor-controlled environment source could replace the remote API or provider host while an inherited credential remained present. The action now validates every repository-selected environment source before authentication or child execution, requires protected credentials and their destinations to come from trusted workflow state, and prevents relevant config/env changes from being incorrectly skipped.

The config preflight intentionally rejects JavaScript/TypeScript configs, config globs/templates, ambiguous extended refs, additional implicit promptfooconfig.*/redteam.* configs, escaping symlinks/traversal, non-regular or oversized inputs, and unsafe YAML/ref forms. Static YAML/JSON and ordinary application variables remain supported.

Compatibility and overlap

Trusted custom Promptfoo hosts remain supported. Later selected env files retain their documented override behavior for ordinary application variables, and unrelated changes still skip cleanly.

The bounded dependency-relevance work overlaps the config/dependency lanes in #974, #979, and #981; those branches should restack/fix-forward rather than reintroducing the earlier skip behavior. General URL/redirect/SSRF hardening remains separately tracked.

Validation

  • regressions cover protected auth/router/cache/OpenSSL keys, delayed env loading, vault precedence and safe-symlink semantics, early/per-file/post-config credential masking, lexical/real env-file containment, YAML/JSON/local refs/fragments and bounded ref traversal, deletions, symlinks, implicit configs, foreign file URLs, executable/dynamic and Nunjucks-delimited fail-closed paths, inherited and generator-config file dependencies, context-sensitive YAML aliases, overlapping relative/absolute prompt globs with bounded brace expansion (including character-class and padded ranges), bounded diagnostics, and binary payload traversal
  • bundled-action and source-level paths reject repository-selected capture origins before authentication or child execution while preserving trusted workflow authentication
  • npm run all: 10 test files, 1,341 tests, 100% statements/branches/functions/lines
  • final dist/ comes from the GitHub check-dist artifact using the locked dependencies; no Socket bypass or dependency overlay

Copy link
Copy Markdown
Contributor Author

@codex review

Please review the exact current head: d5f0e523a024a033469d69b1cb1be3100348c9e1, focusing on authentication provenance and the preserved trusted self-hosted flow.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: d5f0e523a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the GitHub Action’s .env loading boundary so repository-controlled env files cannot override Promptfoo authentication settings (API key / remote API base URL) prior to authentication validation, addressing the reported security finding.

Changes:

  • Parse each configured env file into an isolated object, validate it doesn’t contain protected Promptfoo auth variables, then apply values to process.env.
  • Document the new constraint for env-files in README.md and action.yml.
  • Regenerate the bundled dist/index.js and add tests covering protected-key rejection and ordinary variable forwarding.

Reviewed changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/main.ts Loads .env files into an isolated object, rejects protected auth keys, then applies safe values.
tests/main.test.ts Adds test coverage for protected auth key rejection and allowed env propagation.
README.md Clarifies that Promptfoo auth variables must be set via the workflow environment, not .env files.
action.yml Updates env-files input description to match the new security boundary.
dist/index.js Rebuilt bundle reflecting the updated env-file handling logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread __tests__/main.test.ts
Consolidate the auth-boundary protection into the shared env-file loader
introduced for the process-control fix, instead of a parallel inline check
in main.ts. loadEnvironmentFile now also rejects PROMPTFOO_API_KEY and
PROMPTFOO_REMOTE_API_BASE_URL (case-insensitively) with an auth-specific
message, so a checked-in file cannot pair an inherited credential with an
attacker-chosen host that the preflight would send the bearer token to.

- add FORBIDDEN_AUTH_KEYS + findForbiddenAuthKey to env.ts; check it in
  loadEnvironmentFile after the process-control check
- unit tests (real dotenv) for findForbiddenAuthKey and auth-key isolation
- integration tests: reject both auth vars, forward non-auth PROMPTFOO_
  settings, preserve trusted workflow authentication
- document the auth-variable rejection in the README env-files note

Stacked on the process-control fix (#971); no duplicate env-loading path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mldangelo
mldangelo force-pushed the mdangelo/codex/sec-auth-host-boundary branch from 23a7306 to 8da37f4 Compare July 14, 2026 04:48
@mldangelo
mldangelo changed the base branch from main to mdangelo/codex/sec-env-startup-injection July 14, 2026 04:48
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

'LD_LIBRARY_PATH',
'LD_PRELOAD',

P1 Badge Block LD_AUDIT from env files

When env-files includes a repository-controlled file on Linux, this blocklist still allows LD_AUDIT. glibc treats that variable as a list of auditor shared objects, and src/main.ts later forwards process.env into child git/npx processes, so a checked-in .so can be loaded before Promptfoo runs instead of being rejected like LD_PRELOAD; please add LD_AUDIT to the forbidden list and cover it with the existing env-file regression tests.


'NODE_EXTRA_CA_CERTS',

P2 Badge Block NODE_TLS_REJECT_UNAUTHORIZED from env files

When a selected repository .env file sets NODE_TLS_REJECT_UNAUTHORIZED=0, this validation accepts it and src/main.ts loads it into process.env before both the Promptfoo API-key validation fetch and the later npx promptfoo child. Node's help documents this variable as disabling TLS certificate validation, so a PR-controlled env file can downgrade TLS for the trusted Promptfoo key and provider API calls instead of being rejected with the other proxy/TLS controls.


targetEnvironment[key] = value;
}

P2 Badge Keep env files from overriding Promptfoo thresholds

When a workflow sets PROMPTFOO_PASS_RATE_THRESHOLD in trusted env: and also loads repository env-files, this merge lets the checked-in file overwrite that gate before src/main.ts reads the threshold and forwards process.env to Promptfoo. A PR can set PROMPTFOO_PASS_RATE_THRESHOLD=0, so workflows that rely on that env threshold (for example while repeat-min-pass suppresses Promptfoo's test-failure exit) can pass despite the trusted threshold being missed; treat action-consumed Promptfoo controls as protected or snapshot trusted values before loading repo env files.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mldangelo-oai
mldangelo-oai requested a review from Copilot July 15, 2026 01:13

Copy link
Copy Markdown
Contributor Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/utils/env.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 10 changed files in this pull request and generated 2 comments.

Comment thread src/utils/env.ts Outdated
Comment on lines +595 to +598
const cachedConfig = loadedConfigs.get(filePath);
if (cachedConfig !== undefined) {
return cachedConfig;
}
Comment thread src/utils/config.ts Outdated
Comment on lines +1165 to +1169
} catch (error) {
if (String(error).includes('resolved path')) {
requiresFullEvaluation = true;
}
if (!warnedUnsafeDependency) {

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 259c1b4c27

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/utils/env.ts Outdated
Comment on lines +864 to +866
const resolvedEntry = path.isAbsolute(entry)
? entry
: path.resolve(path.dirname(lexicalConfigPath), entry);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve config envPath from the runtime working directory

When the selected config lives below working-directory, this resolves a relative commandLineOptions.envPath against the config file directory. The action later runs Promptfoo with cwd: workingDirectory and passes the original config, so Promptfoo loads the raw relative env path from the runtime cwd; for configs/promptfooconfig.yaml with envPath: .env.prod, the preflight validates configs/.env.prod while the child loads ./.env.prod. If the cwd file contains a forbidden endpoint/cache/privacy variable, it bypasses the new preflight and reaches Promptfoo unvetted.

Useful? React with 👍 / 👎.

@mldangelo mldangelo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This adds a separate FORBIDDEN_AUTH_KEYS set + findForbiddenAuthKey() to reject PROMPTFOO_API_KEY and PROMPTFOO_REMOTE_API_BASE_URL from repository env files. The intent is right, but the implementation is almost entirely redundant with the process-control denylist it sits next to: PROMPTFOO_REMOTE_API_BASE_URL is already in FORBIDDEN_ENV_FILE_KEYS, and findForbiddenEnvFileKey runs first in loadEnvironmentFile, so the new auth branch is unreachable for that key. The only net-new behavior is rejecting PROMPTFOO_API_KEY — achievable by adding one entry to the existing set. The new set, function, throw block, and test describe are all dead weight, and the tests that claim to cover the fix actually pass on the base branch. I'd collapse this into a one-line addition (details inline).

Merge/stacking note: Stacked on #971 (base = mdangelo/codex/sec-env-startup-injection), not a conflict — but given the redundancy, consider folding the one-line change into #971 rather than shipping a separate PR.

Additional findings (not on changed lines, noted here):

  • 🟠 P2 · simplification — The entire auth apparatus reduces to adding one entry to the existing denylist — in src/utils/env.ts (near const FORBIDDEN_AUTH_KEYS = new Set([)

    findForbiddenEnvFileKey runs before findForbiddenAuthKey in loadEnvironmentFile, and PROMPTFOO_REMOTE_API_BASE_URL is already in FORBIDDEN_ENV_FILE_KEYS — so the only key findForbiddenAuthKey can ever return is PROMPTFOO_API_KEY, and it's a line-for-line re-implementation of findForbiddenEnvFileKey's mechanism. The whole behavioral change is: reject PROMPTFOO_API_KEY from env files.

Minimal fix: add 'PROMPTFOO_API_KEY', to FORBIDDEN_ENV_FILE_KEYS (alphabetically, before 'PROMPTFOO_CACHE_PATH'); delete FORBIDDEN_AUTH_KEYS, findForbiddenAuthKey, the second throw block, and the findForbiddenAuthKey describe in env.test.ts. If you want an auth-specific hint, append it to the existing error's resolution text. (This also removes the PROMPTFOO_REMOTE_API_BASE_URL duplication the Copilot thread already flagged.)

  • 🟠 P2 · tests — The auth-rejection tests don't exercise the new path — they pass on the base branch — in __tests__/env.test.ts (near test('rejects a protected auth variable without leaking any value', () => {)

    This test (and the test.each in main.test.ts) reject PROMPTFOO_REMOTE_API_BASE_URL, but that key is caught by the pre-existing findForbiddenEnvFileKey check that runs first, so the message is the old sets forbidden process-control variable …, not the new sets protected authentication variable …. The assertions only check the key name appears, so they pass with all of this PR's env.ts changes reverted. The one key that actually reaches the new branch — PROMPTFOO_API_KEY — is exercised only via mocked-dotenv tests. Worse, the PR removed PROMPTFOO_API_KEY from the preserves trusted application credentials fixture, which was the only real-file test feeding that key through loadEnvironmentFile.

Minimal fix: write PROMPTFOO_API_KEY into the .env fixture and assert the message contains protected authentication variable + code === ErrorCodes.INVALID_CONFIGURATION, so the test fails without the change.

PROMPTFOO_API_KEY and PROMPTFOO_REMOTE_API_BASE_URL are already both in
FORBIDDEN_ENV_FILE_KEYS, so findForbiddenAuthKey (and its separate throw) is
fully dead code — findForbiddenEnvFileKey already rejects both. Delete the
parallel apparatus and fold the authentication hint into the shared error's
help text. Repoint the real-dotenv rejection test at PROMPTFOO_API_KEY.

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

Copy link
Copy Markdown
Member

Review + fix applied (702cd4f)

Pushed a simplification to this branch. npm run all passes locally (10 test files, 1337 tests, 100% coverage) and I verified dist/ is deterministic (a fresh npm ci && npm run package produces no diff), so check-dist will pass once CI runs on this stacked PR.

Removed the redundant auth-key apparatus — src/utils/env.ts

On the current head, both PROMPTFOO_API_KEY and PROMPTFOO_REMOTE_API_BASE_URL are already in FORBIDDEN_ENV_FILE_KEYS. That makes the separate FORBIDDEN_AUTH_KEYS set + findForbiddenAuthKey() + its dedicated throw fully dead code — findForbiddenEnvFileKey already rejects both keys. The only thing the apparatus added was a distinct error message, so I:

  • deleted FORBIDDEN_AUTH_KEYS, findForbiddenAuthKey, and the second throw block;
  • folded the authentication hint into the shared error's help text (…process, interpreter, provider-endpoint, authentication, TLS/proxy… + Configure trusted controls (including Promptfoo authentication) in the workflow environment);
  • repointed the real-dotenv rejection test at PROMPTFOO_API_KEY (it previously used PROMPTFOO_REMOTE_API_BASE_URL, which is caught by the pre-existing check, so the test passed even with the auth code reverted — it now actually exercises the rejection path);
  • removed the now-dead findForbiddenAuthKey describe block and import.

Net: ~25 fewer lines, identical security behavior, and a test that fails if the rejection regresses.

Heads-up on stacking

This PR is based on mdangelo/codex/sec-env-startup-injection (#971), which I also just pushed to. Both touch env.ts's denylist, so watch for a small conflict when rebasing #972 onto the updated #971 — they edit different regions (denylist entries/masking in #971, the auth apparatus here), so it should merge cleanly, but worth a look.

My original inline comments predate a branch restack and may read as "outdated"; this reflects the current head.

@mldangelo

Copy link
Copy Markdown
Member

Correction on the stacking note above

My earlier comment said to "watch for a small conflict when rebasing #972 onto #971 … should merge cleanly." That was wrong, and GitHub now shows this PR as CONFLICTING / DIRTY with its base — so I want to give you an accurate picture.

This PR's base is mdangelo/codex/sec-env-startup-injection (#971), but the two branches have diverged by ~7,200 lines across the whole codebase (env.ts, main.ts, config.ts, and all test files), sharing only an old common ancestor (0a1c78e). I verified this is pre-existing — the two branches were already that far apart before either of my one-commit fixes (comparing the pre-fix heads shows the same ~7.2k/4.9k divergence). My env.ts change here is a tiny fraction of that.

So:

Net: treat my commit as a reviewable improvement to #972's head; the base/divergence is a separate reconciliation task on your side.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-24T08:30:28.138865Z a872f22 New commits
🔒 Security Review Completed 2026-08-24T08:32:08.517576Z a872f22 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac084f29f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/utils/env.ts Outdated
Comment on lines +117 to +118
const forbiddenAuthKey = findForbiddenAuthKey(fileEnvironment);
if (forbiddenAuthKey) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Rebuild the distributed action bundle

For workflows consuming the packaged action, action.yml executes dist/index.js, but this commit does not update any dist/ files and the checked-in bundle still loads env files directly without findForbiddenAuthKey or the new protected-authentication rejection. Consequently, the source tests pass while released/tagged action users remain able to set these authentication variables through env-files; regenerate and commit the bundle.

AGENTS.md reference: AGENTS.md:L96-L98

Useful? React with 👍 / 👎.

Comment thread src/utils/env.ts Outdated
Comment on lines +117 to +118
const forbiddenAuthKey = findForbiddenAuthKey(fileEnvironment);
if (forbiddenAuthKey) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preflight every environment file Promptfoo will load

When the checkout contains the implicit workingDirectory/.env or a config selects commandLineOptions.envPath, Promptfoo loads that repository-controlled file itself, bypassing this guard because the only loadEnvironmentFile call in this exact commit is in the action's explicit env-files loop. With a trusted workflow PROMPTFOO_API_KEY, such a file can set PROMPTFOO_REMOTE_API_BASE_URL; the action first validates against the trusted host and then launches Promptfoo with --share, allowing the child to send the inherited key to the repository-selected host. Unlike the earlier path-resolution comment, the fresh evidence here is that this tree contains no implicit/config env preflight at all.

Useful? React with 👍 / 👎.

Comment thread src/utils/env.ts Outdated
Comment on lines +59 to +62
const FORBIDDEN_AUTH_KEYS = new Set([
'PROMPTFOO_API_KEY',
'PROMPTFOO_REMOTE_API_BASE_URL',
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Block every Promptfoo credential-routing variable

Even an explicit env-files file that reaches this check can still redirect an inherited Promptfoo credential because the protected set contains only these two names. Promptfoo also honors PROMPTFOO_CLOUD_API_URL as a cloud API destination, so a repository file can set that variable while the trusted workflow supplies PROMPTFOO_API_KEY; the action's getApiHost() ignores this alias and validates against the default host, but the subsequently launched --share process inherits the attacker-selected cloud endpoint. Add all Promptfoo API-routing aliases to the protected set rather than allowing every other PROMPTFOO_ variable through.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: ac084f29f1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/utils/env.ts Outdated
Comment on lines +59 to +62
const FORBIDDEN_AUTH_KEYS = new Set([
'PROMPTFOO_API_KEY',
'PROMPTFOO_REMOTE_API_BASE_URL',
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Security: Regenerate dist with the authentication guard

For workflows invoking the action normally via uses:, action.yml executes dist/index.js, not this source. This commit leaves that bundle byte-for-byte unchanged, and its env-files path still calls dotenv.config({ override: true }) without findForbiddenAuthKey. Therefore, when a secret-bearing workflow selects a repository env file and sharing remains enabled, a contributor can set only PROMPTFOO_REMOTE_API_BASE_URL; the bundle then sends the inherited PROMPTFOO_API_KEY to that host during validation. Regenerate and commit dist/ so the shipped entrypoint enforces this guard.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9450959603

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/utils/env.ts Outdated
for (const selectedPath of configuredPath.split(',')) {
const trimmedPath = selectedPath.trim();
if (trimmedPath) {
environmentPaths.add(path.resolve(workingDirectory, trimmedPath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve configured env files from the config directory

When the config is below workingDirectory, Promptfoo's resolveConfigs() resolves each relative commandLineOptions.envPath against the config file's directory before evaluation, while this line checks it against the working directory. For example, configs/promptfooconfig.yaml with envPath: .env.prod causes the guard to inspect ./.env.prod, but Promptfoo subsequently loads configs/.env.prod; protected routing variables in the latter therefore bypass preflight and can redirect an inherited Promptfoo key. The fresh evidence relative to the earlier thread is its verified resolver behavior, which shows that the mismatch is the inverse of the previously reported one.

Useful? React with 👍 / 👎.

Comment thread src/utils/env.ts Outdated
}) as {
commandLineOptions?: { envPath?: string | string[] };
} | null;
const selectedPaths = config?.commandLineOptions?.envPath;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Inspect env paths supplied through referenced configs

When a YAML or JSON config obtains commandLineOptions.envPath through Promptfoo's supported local $ref or extended-config resolution, this reads only the raw top-level object and never traverses the referenced config. Promptfoo later dereferences that config and loads its selected env file, so a referenced file can select an env file containing PROMPTFOO_REMOTE_API_BASE_URL or another protected routing variable without this preflight examining it. Resolve the same bounded local config graph that Promptfoo will evaluate before collecting environment paths.

Useful? React with 👍 / 👎.

Comment thread src/utils/env.ts Outdated
Comment on lines +173 to +174
if (fs.existsSync(environmentPath)) {
loadEnvironmentFile(environmentPath, {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preflight vault files even when plaintext files are absent

When trusted workflow state sets DOTENV_KEY and the checkout contains .env.vault without a plaintext .env, dotenv selects and decrypts the vault file directly, but this existence check prevents loadEnvironmentFile() from running at all. Promptfoo subsequently performs its own dotenv startup and loads the vault, so a protected routing variable stored there can bypass the guard and be paired with an inherited PROMPTFOO_API_KEY; the same issue applies to a configured .env.production.vault when envPath names the absent .env.production. Check the vault companion selected by dotenv rather than conditioning preflight solely on the plaintext path.

Useful? React with 👍 / 👎.

Comment thread src/main.ts Outdated
return;
}

preflightPromptfooEnvironmentFiles(configAbsolutePath, workingDirectory);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat changed Promptfoo env files as evaluation inputs

When prompts is configured and a PR changes only workingDirectory/.env or a file selected by commandLineOptions.envPath, neither file is included by extractFileDependencies(), so the unchanged-prompt branch returns before this new preflight call. The check therefore succeeds without evaluating an input that changes Promptfoo's results and without rejecting newly introduced protected authentication settings, allowing such a repository change to merge and break later evaluations. Add these environment paths to dependency relevance before deciding to skip.

Useful? React with 👍 / 👎.

Comment thread src/utils/env.ts Outdated
Comment on lines +163 to +166
for (const selectedPath of configuredPath.split(',')) {
const trimmedPath = selectedPath.trim();
if (trimmedPath) {
environmentPaths.add(path.resolve(workingDirectory, trimmedPath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject computed env paths that cannot be preflighted

When a static YAML config computes commandLineOptions.envPath through Promptfoo templating, this loop treats the template itself as a literal filename while Promptfoo later renders it before loading the environment. For example, env: { PICK: '.env.capture' } with envPath: '{{ env.PICK }}' makes this inspect a nonexistent {{ env.PICK }} path, after which Promptfoo loads .env.capture; protected routing variables in that file can therefore reach the child unchecked. Reject computed env paths or resolve them using the same trusted configuration phase as Promptfoo.

Useful? React with 👍 / 👎.

Comment thread src/utils/env.ts Outdated
for (const selectedPath of configuredPath.split(',')) {
const trimmedPath = selectedPath.trim();
if (trimmedPath) {
environmentPaths.add(path.resolve(workingDirectory, trimmedPath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep config-selected env paths inside the working directory

When repository-controlled config uses an absolute envPath or a traversal such as ../../../../dev/zero, path.resolve() accepts it without checking either lexical or real-path containment, and the subsequent dotenv call synchronously opens that runner path. This lets a pull request make the action read files or special devices outside the checkout, including causing a pre-evaluation hang or memory exhaustion; validate containment, symlink targets, and regular-file bounds before reading.

AGENTS.md reference: AGENTS.md:L56-L60

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4e75e703e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/utils/env.ts Outdated
): void {
const environmentPaths = new Set([path.join(workingDirectory, '.env')]);

if (/\.(?:json|ya?ml)$/i.test(configPath) && fs.existsSync(configPath)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject executable configs that bypass env preflight

When the action uses a supported executable config such as promptfooconfig.js or .ts, this extension gate skips the config entirely—the new test at __tests__/env.test.ts:245-251 explicitly preserves that behavior. Such a config can export commandLineOptions: { envPath: '.env.capture' }; Promptfoo then loads that file, while this preflight checks only the implicit .env, allowing protected routing variables in the selected file to reach the child alongside a trusted PROMPTFOO_API_KEY. Reject executable configs when they cannot be inspected safely, or resolve their environment selection through a trusted equivalent of Promptfoo's loader.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c5bc85d45

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/utils/env.ts Outdated
);
}

if (isStaticConfig && fs.existsSync(configPath)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject config globs that cannot be preflighted

When config is a Promptfoo-supported glob such as configs/*.yaml, the name passes the static-extension test but fs.existsSync(configPath) is false, so no selected config is inspected. Promptfoo subsequently expands the glob and can load an envPath containing a protected routing variable after the action has validated an inherited PROMPTFOO_API_KEY, allowing the child’s authenticated share request to use the unchecked destination. Reject config patterns or expand and inspect every matched config before authentication.

Useful? React with 👍 / 👎.

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.

3 participants