Skip to content

feat: add Netlify function env guard plugin#78

Merged
palmtr3man merged 2 commits into
mainfrom
feat/netlify-function-env-guard
Jul 2, 2026
Merged

feat: add Netlify function env guard plugin#78
palmtr3man merged 2 commits into
mainfrom
feat/netlify-function-env-guard

Conversation

@palmtr3man

@palmtr3man palmtr3man commented Jul 2, 2026

Copy link
Copy Markdown
Owner

This PR adds the Netlify function environment guard plugin including netlify.toml and the plugin directory files, leaving out the SendGrid integration changes as requested.

Summary by CodeRabbit

  • New Features

    • Added a Netlify guardrail that checks for environment drift between deployment settings and secret storage.
    • Introduced stricter build-time protection for oversized environment payloads to help prevent deployment failures.
    • Updated deployment routing so the www domain now redirects to the primary site.
  • Bug Fixes

    • Improved drift detection to report missing values more accurately across environments.
    • Added safeguards to fail builds or warn when required configuration is out of sync.

palmtr3man and others added 2 commits June 27, 2026 14:44
Add root package.json and compare Infisical staging /tuj keys against
Netlify production env via API instead of the GHA runner process.env.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prevent Lambda deploy failures by trimming non-function env vars before the 4KB payload limit.

Co-authored-by: Cursor <cursoragent@cursor.com>
@haystack-code-reviewer-pr-hook

Copy link
Copy Markdown

Try Haystack Code Reviewer

Want AI-powered code review for this PR? Get instant analysis, interactive visualizations, and actionable insights.

Review this PR with Haystack

@netlify

netlify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploy Preview for corporate-games-tech-app-production-target-primary ready!

Name Link
🔨 Latest commit 56f2d64
🔍 Latest deploy log https://app.netlify.com/projects/corporate-games-tech-app-production-target-primary/deploys/6a4682a425e87e0009066b39
😎 Deploy Preview https://deploy-preview-78--corporate-games-tech-app-production-target-primary.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR rewrites the drift-check script to compare Infisical secrets against Netlify environment variables using a new parity manifest, updates the CI workflow and package/tsconfig files to run it, and adds a Netlify build plugin that trims environment variables to avoid exceeding Lambda payload limits, plus netlify.toml/gitignore updates.

Changes

Drift-check tooling

Layer / File(s) Summary
Parity manifest
scripts/parity-manifest.ts
Defines P0_KEYS, P1_KEYS, ALIAS_GROUPS, and ALL_MANIFEST_KEYS describing expected Netlify keys and alias equivalences.
Drift-check script rewrite
scripts/drift-check.ts
Replaces prior logic with fetchInfisicalKeys, fetchNetlifyKeys, alias-aware isKeyPresent, reportDrift, and rewritten runDriftCheck comparing Infisical and Netlify keys.
Workflow/package/tsconfig wiring
.github/workflows/drift-check.yml, package.json, tsconfig.json
Workflow step adds NETLIFY_AUTH_TOKEN/NETLIFY_SITE_ID and runs npm run drift-check; new package.json defines scripts/deps; tsconfig.json configures TypeScript for the scripts directory.

Estimated code review effort: 4 (Complex) | ~55 minutes

Netlify config and function env guard plugin

Layer / File(s) Summary
Function env guard plugin
plugins/netlify-plugin-function-env-guard/index.js, manifest.yml, package.json
New Netlify plugin classifies and trims environment variables during onPostBuild, failing the build if the estimated Lambda payload still exceeds the hard byte limit.
netlify.toml and gitignore updates
netlify.toml, .gitignore
Registers the plugin path, replaces placeholder redirects with a www.corporate-games.techcorporate-games.tech redirect, and ignores node_modules/, env files, and .netlify/.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as GitHub Actions
  participant Script as runDriftCheck
  participant Infisical
  participant Netlify

  Workflow->>Script: npm run drift-check
  Script->>Infisical: fetchInfisicalKeys()
  Infisical-->>Script: secret keys
  Script->>Netlify: fetchNetlifyKeys()
  Netlify-->>Script: env var keys
  Script->>Script: compute missingOnNetlify / missingInInfisical
  Script->>Workflow: reportDrift() -> exit code or warning
Loading
sequenceDiagram
  participant Netlify as Netlify Build
  participant Plugin as env-guard onPostBuild
  participant Env as process.env

  Netlify->>Plugin: onPostBuild({ utils })
  Plugin->>Env: enumerate entries
  Plugin->>Env: delete build-only/platform vars
  Plugin->>Plugin: estimate function payload size
  alt payload above target
    Plugin->>Env: delete largest trim candidates
  end
  alt payload still above hard limit
    Plugin->>Netlify: utils.build.failBuild()
  end
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names the main change: adding the Netlify function env guard plugin.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/netlify-function-env-guard

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

🧹 Nitpick comments (1)
plugins/netlify-plugin-function-env-guard/index.js (1)

4-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded allowlist duplicates the drift-check parity manifest — risk of the two lists diverging.

Per the stack context, scripts/parity-manifest.ts already defines the canonical set of expected Netlify env var keys (P0_KEYS, P1_KEYS, ALL_MANIFEST_KEYS) used by the drift-check tooling. This file re-declares a nearly-parallel, independently-maintained list (FUNCTION_ENV_NAMES). Any future key addition/removal now has to be kept in sync in two places by hand, which is exactly the kind of drift this PR's drift-check tooling is meant to catch — but this list sits outside that system entirely. Consider extracting the shared key list into a plain JS/JSON module both the TypeScript parity manifest and this CommonJS plugin can import, to keep a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/netlify-plugin-function-env-guard/index.js` around lines 4 - 93, The
hardcoded allowlist in FUNCTION_ENV_NAMES duplicates the canonical Netlify env
key set already defined in scripts/parity-manifest.ts, so the two sources can
drift apart. Refactor the plugin to consume a shared single source of truth for
the env keys, ideally by extracting the manifest keys into a plain JS/JSON
module that both the parity-manifest logic and the Netlify plugin can import.
Keep the plugin’s validation behavior the same, but remove the independently
maintained list and reference the shared keys from the plugin entrypoint in
index.js.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netlify.toml`:
- Around line 33-39: The redirect rule is using an invalid hostname field and
therefore won’t match only the www origin. Update the redirect entry in
netlify.toml to scope the rule via the full origin in the from field, and keep
the existing destination and 301 force behavior intact. Use the [[redirects]]
block to locate the rule and remove the hostname-based scoping from that
redirect.

In `@package.json`:
- Line 2: The package name in package.json looks like a placeholder and should
be replaced with the real project name. Update the "name" field from
"thispagedoesnotexist12345" to the intended package identifier used for this
repository, keeping it consistent with the project’s published or internal
naming.
- Around line 9-15: The Node runtime and typings are out of sync with the
supported target: update the drift-check workflow to use a supported LTS runtime
(22 or 24), align package.json’s `@types/node` version to that same major, and add
an engines field to make the supported Node version explicit. Use the existing
drift-check workflow and package.json devDependencies as the places to update,
and keep the runtime, types, and declared engine version consistent.

In `@plugins/netlify-plugin-function-env-guard/index.js`:
- Around line 95-133: The payload size guard is misclassifying allowlisted
function env vars as platform vars because `isPlatformEnv` uses `startsWith` for
broad prefixes like `NETLIFY` and `SITE_`. Update
`shouldExcludeFromFunctionPayload` and/or `isPlatformEnv` so
`FUNCTION_ENV_NAMES` entries such as `NETLIFY_API_KEY`, `NETLIFY_SITE_ID`, and
`SITE_URL` are counted in `envByteSize` instead of being excluded. Keep
`shouldDeleteFromEnvironment` behavior aligned so these allowlisted names are
neither deleted nor skipped from size accounting.
- Around line 143-151: The size accounting in the environment pruning logic is
using a stale snapshot of process.env, which makes initialSize and trimming
decisions inaccurate. Recompute the entries from the current process.env after
the deletion loop in the guard flow, then derive functionEntries from that fresh
snapshot before calling envByteSize and the trim logic. Update the code around
Object.entries(process.env), shouldDeleteFromEnvironment,
shouldExcludeFromFunctionPayload, and envByteSize so the byte estimate only
reflects variables that still exist.
- Around line 141-186: The environment trimming logic in onPostBuild is too late
to affect the packaged function payload, since process.env is already consumed
by Functions bundling. Move the logic into onBuild in module.exports and mutate
netlifyConfig.build.environment instead of deleting from process.env, using the
same shouldDeleteFromEnvironment, shouldExcludeFromFunctionPayload, canTrim, and
envByteSize flow to identify which variables should be removed before bundling.

In `@scripts/drift-check.ts`:
- Line 2: The drift-check script is recomputing the manifest key list inline
instead of reusing the shared ALL_MANIFEST_KEYS export, which can drift from
parity-manifest.ts over time. Update the imports in scripts/drift-check.ts to
use ALL_MANIFEST_KEYS from parity-manifest.ts and replace the local
deduplication logic in the drift comparison code with that shared symbol so both
paths stay consistent.
- Around line 62-89: `runDriftCheck`/`reportDrift` currently treat all manifest
keys with one global `STRICTNESS`, so P0 keys can be downgraded to warnings on
non-tracked branches. Update the drift-check flow to preserve severity per key
group: keep `P0_KEYS` as always-fail checks and apply branch-based strictness
only to `P1_KEYS`, or otherwise split the reporting so `reportDrift` can emit a
fatal error whenever any P0 key is missing. Use the existing `P0_KEYS`,
`P1_KEYS`, `runDriftCheck`, and `reportDrift` symbols to wire the two-tier logic
through the manifest-key aggregation and error handling.

---

Nitpick comments:
In `@plugins/netlify-plugin-function-env-guard/index.js`:
- Around line 4-93: The hardcoded allowlist in FUNCTION_ENV_NAMES duplicates the
canonical Netlify env key set already defined in scripts/parity-manifest.ts, so
the two sources can drift apart. Refactor the plugin to consume a shared single
source of truth for the env keys, ideally by extracting the manifest keys into a
plain JS/JSON module that both the parity-manifest logic and the Netlify plugin
can import. Keep the plugin’s validation behavior the same, but remove the
independently maintained list and reference the shared keys from the plugin
entrypoint in index.js.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0dd0033c-acef-46f3-aeda-75cf6cc54e98

📥 Commits

Reviewing files that changed from the base of the PR and between b93eb04 and 56f2d64.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • .github/workflows/drift-check.yml
  • .gitignore
  • netlify.toml
  • package.json
  • plugins/netlify-plugin-function-env-guard/index.js
  • plugins/netlify-plugin-function-env-guard/manifest.yml
  • plugins/netlify-plugin-function-env-guard/package.json
  • scripts/drift-check.ts
  • scripts/parity-manifest.ts
  • tsconfig.json

Comment thread netlify.toml
Comment on lines 33 to 39
# Bare-domain → www canonical redirects (HTTP + HTTPS)
[[redirects]]
from = "http://thispagedoesnotexist12345.com/*"
to = "https://www.thispagedoesnotexist12345.com/:splat"
status = 301
force = true

[[redirects]]
from = "https://thispagedoesnotexist12345.com/*"
to = "https://www.thispagedoesnotexist12345.com/:splat"
hostname = "www.corporate-games.tech"
from = "/*"
to = "https://corporate-games.tech/:splat"
status = 301
force = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## netlify.toml excerpt\n'
nl -ba netlify.toml | sed -n '25,45p'

printf '\n## search for redirect-related keys\n'
rg -n 'hostname|redirects|from =|to =' netlify.toml

Repository: palmtr3man/thispagedoesnotexist12345

Length of output: 239


🌐 Web query:

Netlify netlify.toml redirects hostname key documented from full origin hostname ignored

💡 Result:

There is no "hostname" key in the Netlify netlify.toml redirect configuration syntax [1][2]. Netlify's official documentation for [[redirects]] does not support a hostname property to filter or match traffic based on the origin hostname [1][2]. If you are attempting to redirect based on a specific hostname (domain), you should include the full URL (including the protocol and hostname) within the from property of your redirect rule [3]. For example: [[redirects]] from = "https://example.com/*" to = "https://destination.com/:splat" status = 301 force = true If your redirects are being ignored, it is typically due to one of the following reasons: 1. Precedence and File Shadowing: Redirects in a _redirects file (located in your publish directory) are processed before those in netlify.toml [4]. If a rule in _redirects or a system-generated rule (e.g., from a framework like Next.js or Nuxt) matches the path first, subsequent rules may never be evaluated [5][4]. 2. Framework Runtime Overrides: If you are using a framework with a specialized runtime (like Next.js), the framework may generate its own internal redirects and rewrites that take precedence over your netlify.toml rules [5][3]. 3. Syntax Issues: Ensure you are using force = true if you need the redirect to override existing files (preventing file shadowing) [2][6]. If you suspect your rules are being ignored due to host-based matching, ensure you are specifying the full origin in the from field as shown above, rather than attempting to use an unsupported hostname key [3].

Citations:


Use the full origin in from
hostname isn’t a valid [[redirects]] field here, so this rule won’t scope to www.corporate-games.tech. Use the full URL in from instead.

Proposed fix
[[redirects]]
-  hostname = "www.corporate-games.tech"
-  from = "/*"
+  from = "https://www.corporate-games.tech/*"
  to = "https://corporate-games.tech/:splat"
  status = 301
  force = true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Bare-domain → www canonical redirects (HTTP + HTTPS)
[[redirects]]
from = "http://thispagedoesnotexist12345.com/*"
to = "https://www.thispagedoesnotexist12345.com/:splat"
status = 301
force = true
[[redirects]]
from = "https://thispagedoesnotexist12345.com/*"
to = "https://www.thispagedoesnotexist12345.com/:splat"
hostname = "www.corporate-games.tech"
from = "/*"
to = "https://corporate-games.tech/:splat"
status = 301
force = true
[[redirects]]
from = "https://www.corporate-games.tech/*"
to = "https://corporate-games.tech/:splat"
status = 301
force = true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netlify.toml` around lines 33 - 39, The redirect rule is using an invalid
hostname field and therefore won’t match only the www origin. Update the
redirect entry in netlify.toml to scope the rule via the full origin in the from
field, and keep the existing destination and 301 force behavior intact. Use the
[[redirects]] block to locate the rule and remove the hostname-based scoping
from that redirect.

Comment thread package.json
@@ -0,0 +1,16 @@
{
"name": "thispagedoesnotexist12345",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Placeholder-looking package name.

"thispagedoesnotexist12345" reads like a leftover placeholder rather than an intentional project name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 2, The package name in package.json looks like a
placeholder and should be replaced with the real project name. Update the "name"
field from "thispagedoesnotexist12345" to the intended package identifier used
for this repository, keeping it consistent with the project’s published or
internal naming.

Comment thread package.json
Comment on lines +9 to +15
"devDependencies": {
"@infisical/sdk": "^5.0.2",
"@types/node": "^26.0.1",
"tsx": "^4.22.4",
"typescript": "^5.9.3",
"vitest": "^3.2.4"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## package.json\n'
cat -n package.json

printf '\n## .github/workflows/drift-check.yml\n'
cat -n .github/workflows/drift-check.yml

Repository: palmtr3man/thispagedoesnotexist12345

Length of output: 1865


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## scripts/drift-check.ts\n'
cat -n scripts/drift-check.ts

printf '\n## tsconfig.json\n'
cat -n tsconfig.json 2>/dev/null || true

printf '\n## package lock references to `@types/node`\n'
rg -n '"`@types/node`"|node-version|engines' -S package.json .github/workflows scripts tsconfig.json README.md 2>/dev/null || true

Repository: palmtr3man/thispagedoesnotexist12345

Length of output: 5423


🌐 Web query:

Node.js release schedule Node 20 end of life official schedule July 2026

💡 Result:

Node.js 20 reached its official End-of-Life (EOL) on April 30, 2026 [1][2][3]. As of today, July 2, 2026, the Node.js project no longer provides security patches, bug fixes, or any other support for the v20 release line [1][3]. Users are encouraged to migrate to a supported version of Node.js [1].

Citations:


drift-check is pinned to EOL Node 20. Bump .github/workflows/drift-check.yml to a supported LTS (22 or 24) and align package.json’s @types/node to the chosen runtime; add engines to make the target explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 9 - 15, The Node runtime and typings are out of
sync with the supported target: update the drift-check workflow to use a
supported LTS runtime (22 or 24), align package.json’s `@types/node` version to
that same major, and add an engines field to make the supported Node version
explicit. Use the existing drift-check workflow and package.json devDependencies
as the places to update, and keep the runtime, types, and declared engine
version consistent.

Comment on lines +95 to +133
const PLATFORM_PREFIXES = [
'AWS_',
'BRANCH',
'BUILD_',
'COMMIT_',
'CONTEXT',
'DEPLOY_',
'NETLIFY',
'NODE_',
'NPM_',
'PATH',
'PWD',
'REPOSITORY_',
'SITE_',
'URL',
];

const BUILD_ONLY_ENV_NAMES = new Set([
'SECRETS_SCAN_OMIT_KEYS',
'SECRETS_SCAN_OMIT_PATHS',
]);

function envByteSize(entries) {
return entries.reduce((total, [key, value]) => total + Buffer.byteLength(`${key}=${value || ''}`), 0);
}

function isPlatformEnv(name) {
return PLATFORM_PREFIXES.some((prefix) => name === prefix || name.startsWith(prefix));
}

function shouldExcludeFromFunctionPayload(name) {
return BUILD_ONLY_ENV_NAMES.has(name) || isPlatformEnv(name);
}

function shouldDeleteFromEnvironment(name) {
if (BUILD_ONLY_ENV_NAMES.has(name)) return true;
if (isPlatformEnv(name)) return false;
return !FUNCTION_ENV_NAMES.has(name);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

PLATFORM_PREFIXES collides with allowlisted FUNCTION_ENV_NAMES entries, silently excluding required function vars from size accounting.

isPlatformEnv matches by startsWith, and prefixes 'NETLIFY' and 'SITE_' also match NETLIFY_API_KEY, NETLIFY_SITE_ID (lines 36-37), and SITE_URL (line 89) — all explicitly required function env vars. Because shouldExcludeFromFunctionPayload checks isPlatformEnv without first checking FUNCTION_ENV_NAMES membership, these three vars are excluded from the payload size calculation (undercounting the real byte total) while also not being deleted (shouldDeleteFromEnvironment returns false for platform-classified names) — so they still ship to the function, uncounted. This further weakens the accuracy of the guard's threshold check.

🐛 Proposed fix
 function isPlatformEnv(name) {
+  if (FUNCTION_ENV_NAMES.has(name)) return false;
   return PLATFORM_PREFIXES.some((prefix) => name === prefix || name.startsWith(prefix));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const PLATFORM_PREFIXES = [
'AWS_',
'BRANCH',
'BUILD_',
'COMMIT_',
'CONTEXT',
'DEPLOY_',
'NETLIFY',
'NODE_',
'NPM_',
'PATH',
'PWD',
'REPOSITORY_',
'SITE_',
'URL',
];
const BUILD_ONLY_ENV_NAMES = new Set([
'SECRETS_SCAN_OMIT_KEYS',
'SECRETS_SCAN_OMIT_PATHS',
]);
function envByteSize(entries) {
return entries.reduce((total, [key, value]) => total + Buffer.byteLength(`${key}=${value || ''}`), 0);
}
function isPlatformEnv(name) {
return PLATFORM_PREFIXES.some((prefix) => name === prefix || name.startsWith(prefix));
}
function shouldExcludeFromFunctionPayload(name) {
return BUILD_ONLY_ENV_NAMES.has(name) || isPlatformEnv(name);
}
function shouldDeleteFromEnvironment(name) {
if (BUILD_ONLY_ENV_NAMES.has(name)) return true;
if (isPlatformEnv(name)) return false;
return !FUNCTION_ENV_NAMES.has(name);
}
const PLATFORM_PREFIXES = [
'AWS_',
'BRANCH',
'BUILD_',
'COMMIT_',
'CONTEXT',
'DEPLOY_',
'NETLIFY',
'NODE_',
'NPM_',
'PATH',
'PWD',
'REPOSITORY_',
'SITE_',
'URL',
];
const BUILD_ONLY_ENV_NAMES = new Set([
'SECRETS_SCAN_OMIT_KEYS',
'SECRETS_SCAN_OMIT_PATHS',
]);
function envByteSize(entries) {
return entries.reduce((total, [key, value]) => total + Buffer.byteLength(`${key}=${value || ''}`), 0);
}
function isPlatformEnv(name) {
if (FUNCTION_ENV_NAMES.has(name)) return false;
return PLATFORM_PREFIXES.some((prefix) => name === prefix || name.startsWith(prefix));
}
function shouldExcludeFromFunctionPayload(name) {
return BUILD_ONLY_ENV_NAMES.has(name) || isPlatformEnv(name);
}
function shouldDeleteFromEnvironment(name) {
if (BUILD_ONLY_ENV_NAMES.has(name)) return true;
if (isPlatformEnv(name)) return false;
return !FUNCTION_ENV_NAMES.has(name);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/netlify-plugin-function-env-guard/index.js` around lines 95 - 133,
The payload size guard is misclassifying allowlisted function env vars as
platform vars because `isPlatformEnv` uses `startsWith` for broad prefixes like
`NETLIFY` and `SITE_`. Update `shouldExcludeFromFunctionPayload` and/or
`isPlatformEnv` so `FUNCTION_ENV_NAMES` entries such as `NETLIFY_API_KEY`,
`NETLIFY_SITE_ID`, and `SITE_URL` are counted in `envByteSize` instead of being
excluded. Keep `shouldDeleteFromEnvironment` behavior aligned so these
allowlisted names are neither deleted nor skipped from size accounting.

Comment on lines +141 to +186
module.exports = {
onPostBuild({ utils }) {
const entries = Object.entries(process.env);
for (const [name] of entries) {
if (shouldDeleteFromEnvironment(name)) {
delete process.env[name];
}
}

const functionEntries = entries.filter(([name]) => !shouldExcludeFromFunctionPayload(name));
const initialSize = envByteSize(functionEntries);

if (initialSize <= TARGET_MAX_BYTES) {
console.log(`[function-env-guard] Environment payload is ${initialSize} bytes; no trimming needed.`);
return;
}

const candidates = functionEntries
.filter(([name]) => canTrim(name))
.map(([name, value]) => ({
name,
size: Buffer.byteLength(`${name}=${value || ''}`),
}))
.sort((a, b) => b.size - a.size);

let currentSize = initialSize;
let trimmedCount = 0;

for (const candidate of candidates) {
if (currentSize <= TARGET_MAX_BYTES) break;
currentSize -= candidate.size;
delete process.env[candidate.name];
trimmedCount += 1;
}

console.log(
`[function-env-guard] Trimmed ${trimmedCount} non-function environment variable(s); estimated payload ${currentSize} bytes.`
);

if (currentSize > BYTE_LIMIT) {
utils.build.failBuild(
`Function environment is still above AWS Lambda's ${BYTE_LIMIT} byte limit after trimming non-function variables. Move large required function values to function-scoped Netlify environment variables or an external secret store.`
);
}
},
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file with line numbers and surrounding context.
wc -l plugins/netlify-plugin-function-env-guard/index.js
sed -n '1,260p' plugins/netlify-plugin-function-env-guard/index.js

# Search for related Netlify build-plugin or environment usage in the repo.
rg -n "netlifyConfig\.build\.environment|onPostBuild|onBuild|function-env-guard|process\.env" .

# List likely docs/config files.
git ls-files | rg '(^|/)(netlify\.toml|README\.md|docs/|plugins/|package\.json$)'

Repository: palmtr3man/thispagedoesnotexist12345

Length of output: 31738


🌐 Web query:

Netlify build plugin lifecycle onBuild onPostBuild Functions bundling process.env netlifyConfig.build.environment

💡 Result:

The Netlify Build plugin lifecycle is a sequence of events triggered during a site's build process [1][2]. Understanding the interaction between lifecycle stages and functions is essential for tasks like dynamic function generation [3][4]. Lifecycle Events The relevant lifecycle events occur in the following order [1]: 1. onPreBuild: Runs before the build command [1][5]. 2. onBuild: Runs after the build command completes but before the bundling of Functions and Edge Handlers [1]. 3. Functions Bundling: This internal stage processes and packages your site's functions (typically using tools like esbuild) [1][6]. 4. onPostBuild: Runs after the build command, after Functions bundling, and after Edge Handlers bundling are finished, but before the final deploy stage [7][1]. Accessing Environment Variables You can access environment variables within your build plugins using two primary methods [8][9]: - process.env: This is the recommended approach for reading any environment variables (including those from the Netlify UI or netlify.toml) during the build process [9]. It behaves like a standard Node.js environment [10]. - netlifyConfig.build.environment: This object contains a subset of a project’s environment variables, specifically those declared by the user in the Netlify UI or netlify.toml [9]. It is recommended when you need to programmatically modify these values during the build [9][11]. Functions Bundling and Utilities Functions bundling is an automated step in the Netlify build lifecycle [6]. If you need to dynamically inject functions into your build, you can use the built-in functions utility available in your plugin's event handlers [3][4]. By using utils.functions.add(path), you can add function files or directories during the build, which will then be picked up and bundled by the Netlify build system [3][4]. These injected functions are generally processed alongside your project's other functions [12].

Citations:


🌐 Web query:

Netlify build plugins onBuild runs before Functions bundling onPostBuild after bundling official docs

💡 Result:

According to official Netlify documentation, the build process execution order is designed such that Functions bundling occurs after the onBuild event and before the onPostBuild event [1][2]. The sequence of events is as follows: 1. onPreBuild: Runs before the build command [3]. 2. build.command: The project's build command is executed [3]. 3. onBuild: Runs after the build command completes [1][2]. 4. Functions Bundling: Netlify's core process performs functions bundling [3]. 5. onPostBuild: Runs after the build command completes and after functions bundling has been executed [1][2]. Consequently, if you use an onBuild event handler, it will execute before your functions are bundled, while an onPostBuild event handler will execute after the bundling process is finished [1][2].

Citations:


🌐 Web query:

Netlify docs netlifyConfig.build.environment modify values during build process build plugins official

💡 Result:

To modify build environment variables during the Netlify build process using official build plugins, you should utilize the netlifyConfig.build.environment object [1][2]. Official documentation recommends modifying netlifyConfig.build.environment over process.env when you need to change variable values during the build process [1][2]. This approach ensures that the environment variables are persisted to the build configuration, which allows the Netlify backend to access and utilize these updated values [3]. Key Implementation Details: 1. Access: Within your build plugin's event handlers (e.g., onPreBuild), you can access the configuration via the netlifyConfig object passed as an argument [1][2][4]. 2. Modification: You can directly assign or update values within netlifyConfig.build.environment [1][2]. Example: onPreBuild({ netlifyConfig }) { netlifyConfig.build.environment.DATABASE_URI = 'your-updated-value'; } 3. process.env vs. netlifyConfig: While modifying process.env is still possible for backward compatibility and can be used to read environment variables during the build, it does not update the site's official build configuration [1][2][3]. Modifying netlifyConfig.build.environment is the preferred method for ensuring configuration consistency across the Netlify platform [3]. Note: netlifyConfig.build.environment contains only the variables declared via the Netlify UI or netlify.toml [1][2]. If you only need to read variables, accessing them via process.env is sufficient [1][2].

Citations:


🌐 Web query:

Netlify build plugins netlifyConfig.build.environment recommended when you need to modify values during build official docs

💡 Result:

When working with Netlify build plugins, there are two primary ways to interact with environment variables, each recommended for different purposes according to official documentation [1][2]. 1. process.env You should use process.env to access environment variables when you only need to retrieve their values during the build process [1]. This object includes all environment variables available to the build, including those set in the Netlify UI, netlify.toml, and other sources [1]. 2. netlifyConfig.build.environment You should use netlifyConfig.build.environment when you need to modify environment variable values during the build process [1]. This object contains a subset of a project’s environment variables—specifically those explicitly declared by a user in the Netlify UI or netlify.toml [1]. Modifying this object allows the build to utilize the updated configuration [1][3]. In summary, use process.env for reading and netlifyConfig.build.environment for modifying environment variables within your build plugins [1].

Citations:


Move this to onBuild and mutate netlifyConfig.build.environment
onPostBuild runs after Functions bundling, so deleting from process.env here won’t change the packaged Lambda env payload. Use onBuild and update netlifyConfig.build.environment for the values you want Netlify to carry into the function bundle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/netlify-plugin-function-env-guard/index.js` around lines 141 - 186,
The environment trimming logic in onPostBuild is too late to affect the packaged
function payload, since process.env is already consumed by Functions bundling.
Move the logic into onBuild in module.exports and mutate
netlifyConfig.build.environment instead of deleting from process.env, using the
same shouldDeleteFromEnvironment, shouldExcludeFromFunctionPayload, canTrim, and
envByteSize flow to identify which variables should be removed before bundling.

Comment on lines +143 to +151
const entries = Object.entries(process.env);
for (const [name] of entries) {
if (shouldDeleteFromEnvironment(name)) {
delete process.env[name];
}
}

const functionEntries = entries.filter(([name]) => !shouldExcludeFromFunctionPayload(name));
const initialSize = envByteSize(functionEntries);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Stale snapshot reused for size/trim accounting — distorts the byte estimate.

entries (line 143) is captured before the deletion loop (144-148) mutates process.env. functionEntries (line 150) is then filtered from that same stale entries array instead of re-reading Object.entries(process.env) after deletion. This means functionEntries/initialSize include variables that were already deleted moments earlier (any non-platform, non-build-only, non-allowlisted var). Consequences:

  • initialSize is inflated with bytes for values no longer present in process.env.
  • Those same "phantom" entries are also canTrim-eligible, so the trim loop can "consume" a fake budget from vars that don't exist, causing currentSize to drop below TARGET_MAX_BYTES/BYTE_LIMIT even though the real remaining payload (allowlisted FUNCTION_ENV_NAMES entries, which are never trimmable) could still exceed the limit — silently passing the guard's failBuild check when it shouldn't.
🐛 Proposed fix
     const functionEntries = entries.filter(([name]) => !shouldExcludeFromFunctionPayload(name));
+    // Recompute from process.env after the deletion pass above, not the stale pre-deletion snapshot.
+    const functionEntries = Object.entries(process.env).filter(([name]) => !shouldExcludeFromFunctionPayload(name));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const entries = Object.entries(process.env);
for (const [name] of entries) {
if (shouldDeleteFromEnvironment(name)) {
delete process.env[name];
}
}
const functionEntries = entries.filter(([name]) => !shouldExcludeFromFunctionPayload(name));
const initialSize = envByteSize(functionEntries);
const entries = Object.entries(process.env);
for (const [name] of entries) {
if (shouldDeleteFromEnvironment(name)) {
delete process.env[name];
}
}
// Recompute from process.env after the deletion pass above, not the stale pre-deletion snapshot.
const functionEntries = Object.entries(process.env).filter(([name]) => !shouldExcludeFromFunctionPayload(name));
const initialSize = envByteSize(functionEntries);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/netlify-plugin-function-env-guard/index.js` around lines 143 - 151,
The size accounting in the environment pruning logic is using a stale snapshot
of process.env, which makes initialSize and trimming decisions inaccurate.
Recompute the entries from the current process.env after the deletion loop in
the guard flow, then derive functionEntries from that fresh snapshot before
calling envByteSize and the trim logic. Update the code around
Object.entries(process.env), shouldDeleteFromEnvironment,
shouldExcludeFromFunctionPayload, and envByteSize so the byte estimate only
reflects variables that still exist.

Comment thread scripts/drift-check.ts
@@ -1,45 +1,130 @@
import { InfisicalClient } from "@infisical/sdk";
import { InfisicalSDK } from "@infisical/sdk";
import { ALIAS_GROUPS, P0_KEYS, P1_KEYS } from "./parity-manifest.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicate manifest-key computation instead of reusing ALL_MANIFEST_KEYS.

parity-manifest.ts already exports ALL_MANIFEST_KEYS = [...new Set([...P0_KEYS, ...P1_KEYS])]. Line 114 recomputes the exact same set inline instead of importing it. If the manifest's dedup/derivation logic changes later, this local copy can silently drift out of sync.

♻️ Proposed fix
-import { ALIAS_GROUPS, P0_KEYS, P1_KEYS } from "./parity-manifest.js";
+import { ALIAS_GROUPS, ALL_MANIFEST_KEYS } from "./parity-manifest.js";
-  const manifestKeys = [...new Set([...P0_KEYS, ...P1_KEYS])];
+  const manifestKeys = ALL_MANIFEST_KEYS;

Also applies to: 109-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/drift-check.ts` at line 2, The drift-check script is recomputing the
manifest key list inline instead of reusing the shared ALL_MANIFEST_KEYS export,
which can drift from parity-manifest.ts over time. Update the imports in
scripts/drift-check.ts to use ALL_MANIFEST_KEYS from parity-manifest.ts and
replace the local deduplication logic in the drift comparison code with that
shared symbol so both paths stay consistent.

Comment thread scripts/drift-check.ts
Comment on lines +62 to +89
function reportDrift(
strictness: string,
missingOnNetlify: string[],
missingInInfisical: string[]
): void {
const issues: string[] = [];

if (missingOnNetlify.length > 0) {
issues.push(`missing on Netlify: ${missingOnNetlify.join(", ")}`);
}
if (missingInInfisical.length > 0) {
issues.push(`missing in Infisical ${INFISICAL_PATH}: ${missingInInfisical.join(", ")}`);
}

if (issues.length === 0) {
console.log("✅ No drift detected. Netlify production matches Infisical staging manifest.");
return;
}

const message = `[Drift Detected] ${issues.join("; ")}`;

if (strictness === "fail") {
console.error(`❌ FATAL: ${message}`);
process.exit(1);
}

console.warn(`⚠️ WARNING: ${message}`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

P0 vs. P1 severity tiers are never actually enforced.

parity-manifest.ts documents P0_KEYS as "P0 — hard fail when missing on Netlify (staging/prod)" and P1_KEYS as "P1 — parity / namespace keys; hard fail on main/develop/PR", implying P0 should always be a hard failure while P1 is conditional on branch. However, runDriftCheck/reportDrift merge both sets into a single manifestKeys list (line 114) and apply one global strictness value (from STRICTNESS) uniformly — there is no per-key severity logic. On any branch other than main/develop/PR, STRICTNESS is 'warn', so even a missing P0 key (e.g. SUPABASE_SERVICE_ROLE_KEY) only produces a warning instead of the documented hard fail.

This defeats the purpose of having two severity tiers and could let a P0 key silently stay missing on Netlify production for any non-tracked branch build.

Also applies to: 109-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/drift-check.ts` around lines 62 - 89, `runDriftCheck`/`reportDrift`
currently treat all manifest keys with one global `STRICTNESS`, so P0 keys can
be downgraded to warnings on non-tracked branches. Update the drift-check flow
to preserve severity per key group: keep `P0_KEYS` as always-fail checks and
apply branch-based strictness only to `P1_KEYS`, or otherwise split the
reporting so `reportDrift` can emit a fatal error whenever any P0 key is
missing. Use the existing `P0_KEYS`, `P1_KEYS`, `runDriftCheck`, and
`reportDrift` symbols to wire the two-tier logic through the manifest-key
aggregation and error handling.

@palmtr3man palmtr3man merged commit b5ef66c into main Jul 2, 2026
5 checks passed
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