shakapacker 9.4.0 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -2
- data/CLAUDE.md +4 -0
- data/bin/shakapacker-config +11 -0
- data/docs/configuration.md +51 -8
- data/docs/precompile_hook.md +1 -1
- data/lib/shakapacker/version.rb +1 -1
- data/package/plugins/envFilter.ts +82 -0
- data/package/plugins/rspack.ts +4 -1
- data/package/plugins/webpack.ts +4 -1
- data/package.json +1 -1
- data/shakapacker.gemspec +1 -1
- data/sig/shakapacker/compiler.rbs +1 -0
- data/test/package/plugins/envFiltering.test.js +453 -0
- metadata +6 -3
- data/Gemfile.lock +0 -254
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b214b9c8a64b8d7a39fa119818f9cd85e872abf2278ca9659685ebfe73d3324
|
|
4
|
+
data.tar.gz: 06a7f954d5909f19df8cf13188b398e0b3ce2d5b4b17c6d74eca65e8573e6a30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f728d9d77e03e1c8113edefdd426fd96b900eb64ce7c8e6470cd2e78f45e2f326645d58c31a95dc383809cfebef6af157cd06c145c9204d9975368212e842cb1
|
|
7
|
+
data.tar.gz: 7feac095bbf2182d7e87c21fd8f739cbf774ae1bdb8ec55dbe297e76b154f00b81cbabb3ee6f838dda9fd42b67fe718a749b4778924cf23c8f65b896e6a1b7da
|
data/CHANGELOG.md
CHANGED
|
@@ -11,9 +11,49 @@
|
|
|
11
11
|
|
|
12
12
|
Changes since the last non-beta release.
|
|
13
13
|
|
|
14
|
+
_None yet._
|
|
15
|
+
|
|
16
|
+
## [v9.5.0] - January 7, 2026
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
|
|
20
|
+
- **CRITICAL: Fixed environment variable leak via EnvironmentPlugin**. [PR #857](https://github.com/shakacode/shakapacker/pull/857) by [justin808](https://github.com/justin808). The default webpack and rspack plugins were passing the entire `process.env` to `EnvironmentPlugin`, which exposed ALL build environment variables (including secrets like `DATABASE_URL`, `AWS_SECRET_ACCESS_KEY`, `RAILS_MASTER_KEY`, etc.) to client-side JavaScript bundles when code referenced `process.env.VARIABLE_NAME`. **Note**: This issue is especially critical with webpack 5.103+ due to a [serialization change](https://github.com/webpack/webpack/commit/eecdeeb746b2f996ed4ab74365dd72c95070196b) that can embed all environment variables into bundles when `import.meta.env` is accessed conditionally. This vulnerability was inherited from webpacker v1.0.0 (January 2017) and has been present in all versions of webpacker and shakapacker. **Action required**: After upgrading, rotate any secrets that may have been exposed in production JavaScript bundles.
|
|
21
|
+
|
|
14
22
|
### Added
|
|
15
23
|
|
|
16
|
-
- **Added `
|
|
24
|
+
- **Added `SHAKAPACKER_PUBLIC_*` prefix convention for client-side environment variables**. [PR #857](https://github.com/shakacode/shakapacker/pull/857) by [justin808](https://github.com/justin808). Any environment variable prefixed with `SHAKAPACKER_PUBLIC_` is automatically exposed to client-side JavaScript. This follows the same convention used by Next.js (`NEXT_PUBLIC_*`) and Vite (`VITE_*`), making it explicit which variables are intended for client-side use.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# These are automatically available in your JavaScript
|
|
28
|
+
export SHAKAPACKER_PUBLIC_API_URL=https://api.example.com
|
|
29
|
+
export SHAKAPACKER_PUBLIC_ANALYTICS_ID=UA-12345
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- **Added `SHAKAPACKER_ENV_VARS` environment variable as escape hatch for extending allowed client-side env vars**. [PR #857](https://github.com/shakacode/shakapacker/pull/857) by [justin808](https://github.com/justin808). Set `SHAKAPACKER_ENV_VARS=VAR1,VAR2,VAR3` to expose additional environment variables to client-side JavaScript beyond the default allowlist (`NODE_ENV`, `RAILS_ENV`, `WEBPACK_SERVE`). Only add non-sensitive variables that are safe to embed in public JavaScript bundles.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **BREAKING: EnvironmentPlugin now uses allowlist instead of exposing all env vars**. [PR #857](https://github.com/shakacode/shakapacker/pull/857) by [justin808](https://github.com/justin808). Only `NODE_ENV`, `RAILS_ENV`, `WEBPACK_SERVE`, and any `SHAKAPACKER_PUBLIC_*` variables are exposed by default. If your client-side code relies on other environment variables, either rename them with the `SHAKAPACKER_PUBLIC_` prefix (recommended), add them via `SHAKAPACKER_ENV_VARS`, or customize your webpack/rspack config. This is a security fix - the previous behavior was dangerous.
|
|
37
|
+
|
|
38
|
+
**Migration examples:**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Option 1 (recommended): Use the SHAKAPACKER_PUBLIC_ prefix
|
|
42
|
+
export SHAKAPACKER_PUBLIC_API_BASE_URL=https://api.example.com
|
|
43
|
+
|
|
44
|
+
# Option 2: Use SHAKAPACKER_ENV_VARS for existing variable names
|
|
45
|
+
SHAKAPACKER_ENV_VARS=API_BASE_URL bundle exec rails assets:precompile
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **Fixed gemspec to exclude Gemfile.lock from published gem**. [PR #856](https://github.com/shakacode/shakapacker/pull/856) by [adrien-k](https://github.com/adrien-k). The gemspec's file pattern now correctly excludes `Gemfile.lock`, preventing vulnerability alerts during Docker image scans caused by outdated pinned versions in the lock file.
|
|
51
|
+
|
|
52
|
+
## [v9.4.0] - November 22, 2025
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **Added `SHAKAPACKER_SKIP_PRECOMPILE_HOOK` environment variable to skip precompile hook**. [PR #850](https://github.com/shakacode/shakapacker/pull/850) by [justin808](https://github.com/justin808). Set `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to skip the precompile hook during compilation. This is useful when using process managers like Foreman or Overmind to run the hook once before starting multiple webpack processes, preventing duplicate hook execution. **Migration tip:** If you have a custom `bin/dev` script that starts multiple webpack processes, you can now run the precompile hook once in the script and set this environment variable to prevent each webpack process from running the hook again. See the [precompile hook documentation](./docs/precompile_hook.md#skipping-the-hook) for implementation examples.
|
|
17
57
|
|
|
18
58
|
## [v9.3.4-beta.0] - November 17, 2025
|
|
19
59
|
|
|
@@ -216,6 +256,7 @@ See the [v9 Upgrade Guide](https://github.com/shakacode/shakapacker/blob/main/do
|
|
|
216
256
|
### ⚠️ Breaking Changes
|
|
217
257
|
|
|
218
258
|
1. **SWC is now the default JavaScript transpiler instead of Babel** ([PR 603](https://github.com/shakacode/shakapacker/pull/603) by [justin808](https://github.com/justin808))
|
|
259
|
+
|
|
219
260
|
- Babel dependencies are no longer included as peer dependencies
|
|
220
261
|
- Improves compilation speed by 20x
|
|
221
262
|
- **Migration for existing projects:**
|
|
@@ -232,6 +273,7 @@ See the [v9 Upgrade Guide](https://github.com/shakacode/shakapacker/blob/main/do
|
|
|
232
273
|
```
|
|
233
274
|
|
|
234
275
|
2. **CSS Modules now use named exports by default** ([PR 599](https://github.com/shakacode/shakapacker/pull/599))
|
|
276
|
+
|
|
235
277
|
- **JavaScript:** Use named imports: `import { className } from './styles.module.css'`
|
|
236
278
|
- **TypeScript:** Use namespace imports: `import * as styles from './styles.module.css'`
|
|
237
279
|
- To keep the old behavior with default imports, see [CSS Modules Export Mode documentation](./docs/css-modules-export-mode.md) for configuration instructions
|
|
@@ -497,6 +539,7 @@ See the [v8 Upgrade Guide](https://github.com/shakacode/shakapacker/blob/main/do
|
|
|
497
539
|
|
|
498
540
|
- Set `source_entry_path` to `packs` and `nested_entries` to `true` in`shakapacker.yml` [PR 284](https://github.com/shakacode/shakapacker/pull/284) by [ahangarha](https://github.com/ahangarha).
|
|
499
541
|
- Dev server configuration is modified to follow [webpack recommended configurations](https://webpack.js.org/configuration/dev-server/) for dev server. [PR276](https://github.com/shakacode/shakapacker/pull/276) by [ahangarha](https://github.com/ahangarha):
|
|
542
|
+
|
|
500
543
|
- Deprecated `https` entry is removed from the default configuration file, allowing to set `server` or `https` as per the project requirements. For more detail, check webpack documentation. The `https` entry can be effective only if there is no `server` entry in the config file.
|
|
501
544
|
- `allowed_hosts` is now set to `auto` instead of `all` by default.
|
|
502
545
|
|
|
@@ -763,7 +806,9 @@ Note: [Rubygem is 6.3.0.pre.rc.1](https://rubygems.org/gems/shakapacker/versions
|
|
|
763
806
|
|
|
764
807
|
See [CHANGELOG.md in rails/webpacker (up to v5.4.3)](https://github.com/rails/webpacker/blob/master/CHANGELOG.md)
|
|
765
808
|
|
|
766
|
-
[Unreleased]: https://github.com/shakacode/shakapacker/compare/v9.
|
|
809
|
+
[Unreleased]: https://github.com/shakacode/shakapacker/compare/v9.5.0...main
|
|
810
|
+
[v9.5.0]: https://github.com/shakacode/shakapacker/compare/v9.4.0...v9.5.0
|
|
811
|
+
[v9.4.0]: https://github.com/shakacode/shakapacker/compare/v9.3.4...v9.4.0
|
|
767
812
|
[v9.3.4-beta.0]: https://github.com/shakacode/shakapacker/compare/v9.3.3...v9.3.4-beta.0
|
|
768
813
|
[v9.3.3]: https://github.com/shakacode/shakapacker/compare/v9.3.2...v9.3.3
|
|
769
814
|
[v9.3.2]: https://github.com/shakacode/shakapacker/compare/v9.3.1...v9.3.2
|
data/CLAUDE.md
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
- Run corresponding RSpec tests when changing source files
|
|
13
13
|
- For example, when changing `lib/shakapacker/foo.rb`, run `spec/shakapacker/foo_spec.rb`
|
|
14
14
|
- Run the full test suite with `bundle exec rspec` before pushing
|
|
15
|
+
- **Use explicit RSpec spy assertions** - prefer `have_received`/`not_to have_received` over indirect counter patterns
|
|
16
|
+
- Good: `expect(Open3).to have_received(:capture3).with(anything, hook_command, anything)`
|
|
17
|
+
- Good: `expect(Open3).not_to have_received(:capture3).with(anything, hook_command, anything)`
|
|
18
|
+
- Avoid: `call_count += 1` followed by `expect(call_count).to eq(1)`
|
|
15
19
|
|
|
16
20
|
## Code Style
|
|
17
21
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Minimal shim - all logic is in the TypeScript module
|
|
4
|
+
const { run } = require("shakapacker/configExporter")
|
|
5
|
+
|
|
6
|
+
run(process.argv.slice(2))
|
|
7
|
+
.then((exitCode) => process.exit(exitCode))
|
|
8
|
+
.catch((error) => {
|
|
9
|
+
console.error(error.message)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
})
|
data/docs/configuration.md
CHANGED
|
@@ -617,14 +617,57 @@ Shakapacker validates configuration at runtime and provides helpful error messag
|
|
|
617
617
|
|
|
618
618
|
Some options can be overridden via environment variables:
|
|
619
619
|
|
|
620
|
-
| Variable | Description
|
|
621
|
-
| ---------------------------- |
|
|
622
|
-
| `SHAKAPACKER_CONFIG` | Path to shakapacker.yml
|
|
623
|
-
| `SHAKAPACKER_ASSETS_BUNDLER` | Override assets bundler
|
|
624
|
-
| `SHAKAPACKER_PRECOMPILE` | Override precompile flag
|
|
625
|
-
| `SHAKAPACKER_ASSET_HOST` | Override asset host
|
|
626
|
-
| `
|
|
627
|
-
| `
|
|
620
|
+
| Variable | Description | Example |
|
|
621
|
+
| ---------------------------- | -------------------------------------------------- | ---------------------------- |
|
|
622
|
+
| `SHAKAPACKER_CONFIG` | Path to shakapacker.yml | `config/webpack.yml` |
|
|
623
|
+
| `SHAKAPACKER_ASSETS_BUNDLER` | Override assets bundler | `rspack` |
|
|
624
|
+
| `SHAKAPACKER_PRECOMPILE` | Override precompile flag | `false` |
|
|
625
|
+
| `SHAKAPACKER_ASSET_HOST` | Override asset host | `https://cdn.example.com` |
|
|
626
|
+
| `SHAKAPACKER_PUBLIC_*` | Auto-exposed to client-side JS (prefix convention) | `SHAKAPACKER_PUBLIC_API_URL` |
|
|
627
|
+
| `SHAKAPACKER_ENV_VARS` | Additional env vars to expose to client-side JS | `API_URL,FEATURE_FLAGS` |
|
|
628
|
+
| `NODE_ENV` | Node environment | `production` |
|
|
629
|
+
| `RAILS_ENV` | Rails environment | `staging` |
|
|
630
|
+
|
|
631
|
+
### Exposing Environment Variables to Client-Side JavaScript
|
|
632
|
+
|
|
633
|
+
By default, only `NODE_ENV`, `RAILS_ENV`, and `WEBPACK_SERVE` are exposed to client-side JavaScript via webpack/rspack's `EnvironmentPlugin`. This is a security measure to prevent accidentally leaking secrets like `DATABASE_URL`, `API_SECRET_KEY`, etc. into your JavaScript bundles.
|
|
634
|
+
|
|
635
|
+
#### SHAKAPACKER*PUBLIC*\* Prefix (Recommended)
|
|
636
|
+
|
|
637
|
+
Any environment variable prefixed with `SHAKAPACKER_PUBLIC_` is automatically exposed to client-side code. This follows the same convention used by Next.js (`NEXT_PUBLIC_*`) and Vite (`VITE_*`):
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
# These are automatically available in your JavaScript
|
|
641
|
+
export SHAKAPACKER_PUBLIC_API_URL=https://api.example.com
|
|
642
|
+
export SHAKAPACKER_PUBLIC_ANALYTICS_ID=UA-12345
|
|
643
|
+
export SHAKAPACKER_PUBLIC_FEATURE_FLAGS=dark_mode,beta_ui
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
```javascript
|
|
647
|
+
// Access in your JavaScript code
|
|
648
|
+
console.log(process.env.SHAKAPACKER_PUBLIC_API_URL)
|
|
649
|
+
console.log(process.env.SHAKAPACKER_PUBLIC_ANALYTICS_ID)
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
The prefix makes it explicit which variables are intended for client-side use, preventing accidental exposure of secrets.
|
|
653
|
+
|
|
654
|
+
#### SHAKAPACKER_ENV_VARS (Legacy/Escape Hatch)
|
|
655
|
+
|
|
656
|
+
For variables without the `SHAKAPACKER_PUBLIC_` prefix, you can use `SHAKAPACKER_ENV_VARS` to expose them:
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
# Expose additional variables during build
|
|
660
|
+
SHAKAPACKER_ENV_VARS=API_BASE_URL,FEATURE_FLAGS bundle exec rake assets:precompile
|
|
661
|
+
|
|
662
|
+
# In development
|
|
663
|
+
SHAKAPACKER_ENV_VARS=API_BASE_URL bin/shakapacker-dev-server
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
```javascript
|
|
667
|
+
// These are available after adding to SHAKAPACKER_ENV_VARS
|
|
668
|
+
console.log(process.env.API_BASE_URL)
|
|
669
|
+
console.log(process.env.FEATURE_FLAGS)
|
|
670
|
+
```
|
|
628
671
|
|
|
629
672
|
## Best Practices
|
|
630
673
|
|
data/docs/precompile_hook.md
CHANGED
|
@@ -300,7 +300,7 @@ This is useful when:
|
|
|
300
300
|
- Running the hook manually and then compiling multiple times
|
|
301
301
|
- Debugging compilation issues without the hook
|
|
302
302
|
|
|
303
|
-
**Note:** The examples below show how to implement this in your custom `bin/dev` script. If you're using React on Rails
|
|
303
|
+
**Note:** The examples below show how to implement this in your custom `bin/dev` script. If you're using React on Rails v13.1.0+, the generated `bin/dev` script already implements this pattern automatically - **no action needed**. It runs the precompile hook once before launching processes, then sets `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to prevent duplicate execution.
|
|
304
304
|
|
|
305
305
|
**Recommended: Use Procfile env prefix**
|
|
306
306
|
|
data/lib/shakapacker/version.rb
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared environment variable filtering logic for webpack and rspack plugins.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: This module ensures only allowlisted environment variables are
|
|
5
|
+
* exposed to client-side JavaScript bundles, preventing accidental leakage
|
|
6
|
+
* of secrets like DATABASE_URL, API keys, etc.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Allowlist of environment variables that are safe to expose to client-side JavaScript.
|
|
11
|
+
*
|
|
12
|
+
* SECURITY: Never add sensitive variables like DATABASE_URL, API keys, or secrets.
|
|
13
|
+
* These values are embedded directly into the JavaScript bundle and are publicly visible.
|
|
14
|
+
*
|
|
15
|
+
* Users can extend this list via:
|
|
16
|
+
* 1. SHAKAPACKER_PUBLIC_* prefix (auto-exposed, similar to Next.js/Vite conventions)
|
|
17
|
+
* 2. SHAKAPACKER_ENV_VARS environment variable (comma-separated list)
|
|
18
|
+
* 3. Customizing their webpack/rspack config
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_ALLOWED_ENV_VARS = [
|
|
21
|
+
"NODE_ENV",
|
|
22
|
+
"RAILS_ENV",
|
|
23
|
+
"WEBPACK_SERVE"
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Prefix for environment variables that are automatically exposed to client-side code.
|
|
28
|
+
* Similar to Next.js's NEXT_PUBLIC_ and Vite's VITE_ prefixes.
|
|
29
|
+
*
|
|
30
|
+
* Example: SHAKAPACKER_PUBLIC_API_URL will be available as process.env.SHAKAPACKER_PUBLIC_API_URL
|
|
31
|
+
*/
|
|
32
|
+
export const PUBLIC_ENV_PREFIX = "SHAKAPACKER_PUBLIC_"
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gets the list of environment variables to expose to client-side code.
|
|
36
|
+
* Combines:
|
|
37
|
+
* 1. Default allowed vars (NODE_ENV, RAILS_ENV, WEBPACK_SERVE)
|
|
38
|
+
* 2. Any vars with SHAKAPACKER_PUBLIC_ prefix (auto-exposed)
|
|
39
|
+
* 3. Any user-specified vars from SHAKAPACKER_ENV_VARS
|
|
40
|
+
*/
|
|
41
|
+
export const getAllowedEnvVars = (): string[] => {
|
|
42
|
+
const allowed: string[] = [...DEFAULT_ALLOWED_ENV_VARS]
|
|
43
|
+
|
|
44
|
+
// Auto-expose any SHAKAPACKER_PUBLIC_* variables (similar to Next.js/Vite convention)
|
|
45
|
+
Object.keys(process.env).forEach((key) => {
|
|
46
|
+
if (key.startsWith(PUBLIC_ENV_PREFIX)) {
|
|
47
|
+
allowed.push(key)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Allow users to specify additional env vars via SHAKAPACKER_ENV_VARS
|
|
52
|
+
const userVars = process.env.SHAKAPACKER_ENV_VARS
|
|
53
|
+
if (userVars) {
|
|
54
|
+
const additionalVars = userVars
|
|
55
|
+
.split(",")
|
|
56
|
+
.map((v) => v.trim())
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
|
|
59
|
+
allowed.push(...additionalVars)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Remove duplicates (can occur if same var is in multiple sources)
|
|
63
|
+
return [...new Set(allowed)]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builds a filtered environment object containing only allowed variables.
|
|
68
|
+
* Returns an object with variable names as keys and their values.
|
|
69
|
+
* Uses null as default for missing variables (webpack/rspack treat null as optional).
|
|
70
|
+
*/
|
|
71
|
+
export const getFilteredEnv = (): Record<string, string | null> => {
|
|
72
|
+
const allowedVars = getAllowedEnvVars()
|
|
73
|
+
const filtered: Record<string, string | null> = {}
|
|
74
|
+
|
|
75
|
+
for (const varName of allowedVars) {
|
|
76
|
+
// Use null as default for missing vars - webpack/rspack treat null as optional
|
|
77
|
+
// (undefined would cause them to throw if the var is used but not set)
|
|
78
|
+
filtered[varName] = process.env[varName] ?? null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return filtered
|
|
82
|
+
}
|
data/package/plugins/rspack.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Config } from "../types"
|
|
2
|
+
import { getFilteredEnv } from "./envFilter"
|
|
2
3
|
|
|
3
4
|
const { requireOrError } = require("../utils/requireOrError")
|
|
4
5
|
|
|
@@ -29,7 +30,9 @@ interface Manifest {
|
|
|
29
30
|
|
|
30
31
|
const getPlugins = (): unknown[] => {
|
|
31
32
|
const plugins = [
|
|
32
|
-
|
|
33
|
+
// SECURITY: Only expose allowlisted environment variables to prevent secrets leaking
|
|
34
|
+
// into client-side bundles. See envFilter.ts for the allowlist configuration.
|
|
35
|
+
new rspack.EnvironmentPlugin(getFilteredEnv()),
|
|
33
36
|
new RspackManifestPlugin({
|
|
34
37
|
fileName: config.manifestPath.split("/").pop(), // Get just the filename
|
|
35
38
|
publicPath: config.publicPathWithoutCDN,
|
data/package/plugins/webpack.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Config } from "../types"
|
|
2
|
+
import { getFilteredEnv } from "./envFilter"
|
|
2
3
|
|
|
3
4
|
const { requireOrError } = require("../utils/requireOrError")
|
|
4
5
|
// TODO: Change to `const { WebpackAssetsManifest }` when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
|
|
@@ -15,7 +16,9 @@ const getPlugins = (): unknown[] => {
|
|
|
15
16
|
? WebpackAssetsManifest.WebpackAssetsManifest
|
|
16
17
|
: WebpackAssetsManifest
|
|
17
18
|
const plugins = [
|
|
18
|
-
|
|
19
|
+
// SECURITY: Only expose allowlisted environment variables to prevent secrets leaking
|
|
20
|
+
// into client-side bundles. See envFilter.ts for the allowlist configuration.
|
|
21
|
+
new webpack.EnvironmentPlugin(getFilteredEnv()),
|
|
19
22
|
new WebpackAssetsManifestConstructor({
|
|
20
23
|
merge: true,
|
|
21
24
|
entrypoints: true,
|
data/package.json
CHANGED
data/shakapacker.gemspec
CHANGED
|
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
|
|
|
29
29
|
s.add_development_dependency "rubocop-performance"
|
|
30
30
|
|
|
31
31
|
s.files = `git ls-files -z`.split("\x0").reject { |f|
|
|
32
|
-
f.match(%r{^(test|spec|features|tmp|node_modules|packages|coverage|Gemfile.lock|rakelib)
|
|
32
|
+
f.match(%r{^(test|spec|features|tmp|node_modules|packages|coverage|Gemfile.lock|rakelib)($|/)})
|
|
33
33
|
} + Dir.glob("sig/**/*.rbs")
|
|
34
34
|
|
|
35
35
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security tests for environment variable filtering in EnvironmentPlugin.
|
|
3
|
+
*
|
|
4
|
+
* These tests verify that only allowlisted environment variables are exposed
|
|
5
|
+
* to client-side JavaScript bundles, preventing accidental leakage of secrets.
|
|
6
|
+
*
|
|
7
|
+
* CVE: Environment variables leak via EnvironmentPlugin(process.env)
|
|
8
|
+
* See: https://github.com/shakacode/shakapacker/security/advisories
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs")
|
|
12
|
+
const path = require("path")
|
|
13
|
+
|
|
14
|
+
const pluginsDir = path.resolve(__dirname, "../../../package/plugins")
|
|
15
|
+
|
|
16
|
+
describe("environment variable filtering security", () => {
|
|
17
|
+
const originalEnv = { ...process.env }
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Set up test environment with sensitive variables
|
|
21
|
+
process.env.NODE_ENV = "production"
|
|
22
|
+
process.env.RAILS_ENV = "production"
|
|
23
|
+
process.env.WEBPACK_SERVE = "false"
|
|
24
|
+
|
|
25
|
+
// Simulate sensitive build environment variables
|
|
26
|
+
process.env.DATABASE_URL = "postgres://user:password@host/db"
|
|
27
|
+
process.env.AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
|
|
28
|
+
process.env.AWS_SECRET_ACCESS_KEY =
|
|
29
|
+
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
30
|
+
process.env.RAILS_MASTER_KEY = "abc123secretmasterkey456"
|
|
31
|
+
process.env.STRIPE_SECRET_KEY = "sk_live_secretkey123"
|
|
32
|
+
process.env.SESSION_SECRET = "supersecrettoken"
|
|
33
|
+
|
|
34
|
+
// Clear any cached modules
|
|
35
|
+
jest.resetModules()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
// Restore original environment
|
|
40
|
+
Object.keys(process.env).forEach((key) => {
|
|
41
|
+
if (!(key in originalEnv)) {
|
|
42
|
+
delete process.env[key]
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
Object.assign(process.env, originalEnv)
|
|
46
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe("shared envFilter module", () => {
|
|
50
|
+
it("exists and exports the filtering functions", () => {
|
|
51
|
+
const envFilterSource = fs.readFileSync(
|
|
52
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
53
|
+
"utf8"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// Verify exports
|
|
57
|
+
expect(envFilterSource).toContain("export const DEFAULT_ALLOWED_ENV_VARS")
|
|
58
|
+
expect(envFilterSource).toContain("export const getAllowedEnvVars")
|
|
59
|
+
expect(envFilterSource).toContain("export const getFilteredEnv")
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it("has the default allowlist with only safe variables", () => {
|
|
63
|
+
const envFilterSource = fs.readFileSync(
|
|
64
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
65
|
+
"utf8"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// Extract the DEFAULT_ALLOWED_ENV_VARS array from source
|
|
69
|
+
const allowlistMatch = envFilterSource.match(
|
|
70
|
+
/DEFAULT_ALLOWED_ENV_VARS\s*=\s*\[([\s\S]*?)\]\s*as const/
|
|
71
|
+
)
|
|
72
|
+
expect(allowlistMatch).toBeTruthy()
|
|
73
|
+
|
|
74
|
+
const allowlistContent = allowlistMatch[1]
|
|
75
|
+
|
|
76
|
+
// These patterns should NEVER appear in the allowlist
|
|
77
|
+
const sensitivePatterns = [
|
|
78
|
+
"DATABASE",
|
|
79
|
+
"SECRET",
|
|
80
|
+
"PASSWORD",
|
|
81
|
+
"CREDENTIAL",
|
|
82
|
+
"AWS_",
|
|
83
|
+
"STRIPE",
|
|
84
|
+
"MASTER"
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
sensitivePatterns.forEach((pattern) => {
|
|
88
|
+
expect(allowlistContent.toUpperCase()).not.toContain(pattern)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Verify expected safe vars are present
|
|
92
|
+
expect(allowlistContent).toContain("NODE_ENV")
|
|
93
|
+
expect(allowlistContent).toContain("RAILS_ENV")
|
|
94
|
+
expect(allowlistContent).toContain("WEBPACK_SERVE")
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it("includes SHAKAPACKER_ENV_VARS extension support", () => {
|
|
98
|
+
const envFilterSource = fs.readFileSync(
|
|
99
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
100
|
+
"utf8"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expect(envFilterSource).toContain("SHAKAPACKER_ENV_VARS")
|
|
104
|
+
expect(envFilterSource).toContain('split(",")')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("exports PUBLIC_ENV_PREFIX constant", () => {
|
|
108
|
+
const envFilterSource = fs.readFileSync(
|
|
109
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
110
|
+
"utf8"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
expect(envFilterSource).toContain("export const PUBLIC_ENV_PREFIX")
|
|
114
|
+
expect(envFilterSource).toContain('SHAKAPACKER_PUBLIC_"')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("auto-exposes SHAKAPACKER_PUBLIC_* variables", () => {
|
|
118
|
+
const envFilterSource = fs.readFileSync(
|
|
119
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
120
|
+
"utf8"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// Verify the prefix check is present
|
|
124
|
+
expect(envFilterSource).toContain("startsWith(PUBLIC_ENV_PREFIX)")
|
|
125
|
+
expect(envFilterSource).toContain("Object.keys(process.env)")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it("uses SHAKAPACKER_PUBLIC_ prefix (with trailing underscore) to prevent system var exposure", () => {
|
|
129
|
+
const envFilterSource = fs.readFileSync(
|
|
130
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
131
|
+
"utf8"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
// SECURITY: The prefix MUST include the trailing underscore to ensure
|
|
135
|
+
// Shakapacker system variables like SHAKAPACKER_CONFIG, SHAKAPACKER_ENV_VARS,
|
|
136
|
+
// SHAKAPACKER_PRECOMPILE, etc. are NOT accidentally exposed.
|
|
137
|
+
// Only SHAKAPACKER_PUBLIC_* variables should be auto-exposed.
|
|
138
|
+
const prefixMatch = envFilterSource.match(
|
|
139
|
+
/PUBLIC_ENV_PREFIX\s*=\s*["']([^"']+)["']/
|
|
140
|
+
)
|
|
141
|
+
expect(prefixMatch).toBeTruthy()
|
|
142
|
+
expect(prefixMatch[1]).toBe("SHAKAPACKER_PUBLIC_")
|
|
143
|
+
|
|
144
|
+
// Verify the trailing underscore is present - this is critical for security
|
|
145
|
+
expect(prefixMatch[1]).toMatch(/_$/)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it("handles whitespace and empty values in CSV", () => {
|
|
149
|
+
const envFilterSource = fs.readFileSync(
|
|
150
|
+
path.join(pluginsDir, "envFilter.ts"),
|
|
151
|
+
"utf8"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// Verify trim() is called on each value
|
|
155
|
+
expect(envFilterSource).toMatch(/\.map\(\s*\(?v\)?\s*=>\s*v\.trim\(\)/)
|
|
156
|
+
// Verify filter(Boolean) is called to remove empty strings
|
|
157
|
+
expect(envFilterSource).toMatch(/\.filter\(Boolean\)/)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe("webpack plugin", () => {
|
|
162
|
+
it("imports from shared envFilter module", () => {
|
|
163
|
+
const webpackPluginSource = fs.readFileSync(
|
|
164
|
+
path.join(pluginsDir, "webpack.ts"),
|
|
165
|
+
"utf8"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
expect(webpackPluginSource).toContain(
|
|
169
|
+
'import { getFilteredEnv } from "./envFilter"'
|
|
170
|
+
)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("uses getFilteredEnv() not process.env", () => {
|
|
174
|
+
const webpackPluginSource = fs.readFileSync(
|
|
175
|
+
path.join(pluginsDir, "webpack.ts"),
|
|
176
|
+
"utf8"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// SECURITY: Verify the dangerous pattern is NOT present
|
|
180
|
+
expect(webpackPluginSource).not.toMatch(
|
|
181
|
+
/new webpack\.EnvironmentPlugin\(process\.env\)/
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
// Verify the safe pattern IS present
|
|
185
|
+
expect(webpackPluginSource).toMatch(/getFilteredEnv\(\)/)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("does not duplicate the filtering logic", () => {
|
|
189
|
+
const webpackPluginSource = fs.readFileSync(
|
|
190
|
+
path.join(pluginsDir, "webpack.ts"),
|
|
191
|
+
"utf8"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// Should NOT have its own copy of these
|
|
195
|
+
expect(webpackPluginSource).not.toContain("DEFAULT_ALLOWED_ENV_VARS")
|
|
196
|
+
expect(webpackPluginSource).not.toContain("PUBLIC_ENV_PREFIX")
|
|
197
|
+
expect(webpackPluginSource).not.toContain("getAllowedEnvVars")
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe("rspack plugin", () => {
|
|
202
|
+
it("imports from shared envFilter module", () => {
|
|
203
|
+
const rspackPluginSource = fs.readFileSync(
|
|
204
|
+
path.join(pluginsDir, "rspack.ts"),
|
|
205
|
+
"utf8"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
expect(rspackPluginSource).toContain(
|
|
209
|
+
'import { getFilteredEnv } from "./envFilter"'
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it("uses getFilteredEnv() not process.env", () => {
|
|
214
|
+
const rspackPluginSource = fs.readFileSync(
|
|
215
|
+
path.join(pluginsDir, "rspack.ts"),
|
|
216
|
+
"utf8"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
// SECURITY: Verify the dangerous pattern is NOT present
|
|
220
|
+
expect(rspackPluginSource).not.toMatch(
|
|
221
|
+
/new rspack\.EnvironmentPlugin\(process\.env\)/
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
// Verify the safe pattern IS present
|
|
225
|
+
expect(rspackPluginSource).toMatch(/getFilteredEnv\(\)/)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it("does not duplicate the filtering logic", () => {
|
|
229
|
+
const rspackPluginSource = fs.readFileSync(
|
|
230
|
+
path.join(pluginsDir, "rspack.ts"),
|
|
231
|
+
"utf8"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// Should NOT have its own copy of these
|
|
235
|
+
expect(rspackPluginSource).not.toContain("DEFAULT_ALLOWED_ENV_VARS")
|
|
236
|
+
expect(rspackPluginSource).not.toContain("PUBLIC_ENV_PREFIX")
|
|
237
|
+
expect(rspackPluginSource).not.toContain("getAllowedEnvVars")
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe("consistency", () => {
|
|
242
|
+
it("both plugins use the same shared module", () => {
|
|
243
|
+
const webpackPluginSource = fs.readFileSync(
|
|
244
|
+
path.join(pluginsDir, "webpack.ts"),
|
|
245
|
+
"utf8"
|
|
246
|
+
)
|
|
247
|
+
const rspackPluginSource = fs.readFileSync(
|
|
248
|
+
path.join(pluginsDir, "rspack.ts"),
|
|
249
|
+
"utf8"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
// Both should import from the same source
|
|
253
|
+
const webpackImport = webpackPluginSource.match(
|
|
254
|
+
/import\s*{[^}]*getFilteredEnv[^}]*}\s*from\s*["']([^"']+)["']/
|
|
255
|
+
)
|
|
256
|
+
const rspackImport = rspackPluginSource.match(
|
|
257
|
+
/import\s*{[^}]*getFilteredEnv[^}]*}\s*from\s*["']([^"']+)["']/
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
expect(webpackImport).toBeTruthy()
|
|
261
|
+
expect(rspackImport).toBeTruthy()
|
|
262
|
+
expect(webpackImport[1]).toBe(rspackImport[1])
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Runtime behavioral tests that actually call the filtering functions.
|
|
268
|
+
* These complement the static source analysis tests above.
|
|
269
|
+
*/
|
|
270
|
+
describe("runtime behavior", () => {
|
|
271
|
+
// Helper to get fresh module instance (avoiding caching issues)
|
|
272
|
+
const getEnvFilter = () => {
|
|
273
|
+
jest.resetModules()
|
|
274
|
+
return require("../../../package/plugins/envFilter")
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
describe("getAllowedEnvVars", () => {
|
|
278
|
+
it("returns default allowed vars when SHAKAPACKER_ENV_VARS is unset", () => {
|
|
279
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
280
|
+
// Remove any SHAKAPACKER_PUBLIC_* vars from test setup
|
|
281
|
+
const publicVars = Object.keys(process.env).filter((key) =>
|
|
282
|
+
key.startsWith("SHAKAPACKER_PUBLIC_")
|
|
283
|
+
)
|
|
284
|
+
publicVars.forEach((key) => {
|
|
285
|
+
delete process.env[key]
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
289
|
+
const allowed = getAllowedEnvVars()
|
|
290
|
+
|
|
291
|
+
expect(allowed).toContain("NODE_ENV")
|
|
292
|
+
expect(allowed).toContain("RAILS_ENV")
|
|
293
|
+
expect(allowed).toContain("WEBPACK_SERVE")
|
|
294
|
+
expect(allowed).toHaveLength(3)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it("includes SHAKAPACKER_PUBLIC_* variables when present", () => {
|
|
298
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
299
|
+
process.env.SHAKAPACKER_PUBLIC_API_URL = "https://api.example.com"
|
|
300
|
+
process.env.SHAKAPACKER_PUBLIC_ANALYTICS_ID = "UA-12345"
|
|
301
|
+
|
|
302
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
303
|
+
const allowed = getAllowedEnvVars()
|
|
304
|
+
|
|
305
|
+
expect(allowed).toContain("NODE_ENV")
|
|
306
|
+
expect(allowed).toContain("RAILS_ENV")
|
|
307
|
+
expect(allowed).toContain("WEBPACK_SERVE")
|
|
308
|
+
expect(allowed).toContain("SHAKAPACKER_PUBLIC_API_URL")
|
|
309
|
+
expect(allowed).toContain("SHAKAPACKER_PUBLIC_ANALYTICS_ID")
|
|
310
|
+
|
|
311
|
+
// Cleanup
|
|
312
|
+
delete process.env.SHAKAPACKER_PUBLIC_API_URL
|
|
313
|
+
delete process.env.SHAKAPACKER_PUBLIC_ANALYTICS_ID
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it("does NOT include SHAKAPACKER_* system variables (without PUBLIC_)", () => {
|
|
317
|
+
process.env.SHAKAPACKER_CONFIG = "/custom/path"
|
|
318
|
+
process.env.SHAKAPACKER_PRECOMPILE = "true"
|
|
319
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
320
|
+
|
|
321
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
322
|
+
const allowed = getAllowedEnvVars()
|
|
323
|
+
|
|
324
|
+
expect(allowed).not.toContain("SHAKAPACKER_CONFIG")
|
|
325
|
+
expect(allowed).not.toContain("SHAKAPACKER_PRECOMPILE")
|
|
326
|
+
|
|
327
|
+
// Cleanup
|
|
328
|
+
delete process.env.SHAKAPACKER_CONFIG
|
|
329
|
+
delete process.env.SHAKAPACKER_PRECOMPILE
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it("parses SHAKAPACKER_ENV_VARS CSV and includes those variables", () => {
|
|
333
|
+
process.env.SHAKAPACKER_ENV_VARS = "CUSTOM_VAR1,CUSTOM_VAR2,ANOTHER_VAR"
|
|
334
|
+
|
|
335
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
336
|
+
const allowed = getAllowedEnvVars()
|
|
337
|
+
|
|
338
|
+
expect(allowed).toContain("CUSTOM_VAR1")
|
|
339
|
+
expect(allowed).toContain("CUSTOM_VAR2")
|
|
340
|
+
expect(allowed).toContain("ANOTHER_VAR")
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it("handles whitespace in SHAKAPACKER_ENV_VARS CSV", () => {
|
|
344
|
+
process.env.SHAKAPACKER_ENV_VARS = " VAR1 , VAR2 , VAR3 "
|
|
345
|
+
|
|
346
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
347
|
+
const allowed = getAllowedEnvVars()
|
|
348
|
+
|
|
349
|
+
expect(allowed).toContain("VAR1")
|
|
350
|
+
expect(allowed).toContain("VAR2")
|
|
351
|
+
expect(allowed).toContain("VAR3")
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it("ignores empty entries in SHAKAPACKER_ENV_VARS CSV", () => {
|
|
355
|
+
process.env.SHAKAPACKER_ENV_VARS = "VAR1,,VAR2,,,VAR3,"
|
|
356
|
+
|
|
357
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
358
|
+
const allowed = getAllowedEnvVars()
|
|
359
|
+
|
|
360
|
+
expect(allowed).toContain("VAR1")
|
|
361
|
+
expect(allowed).toContain("VAR2")
|
|
362
|
+
expect(allowed).toContain("VAR3")
|
|
363
|
+
// Should not contain empty strings
|
|
364
|
+
expect(allowed.filter((v) => v === "")).toHaveLength(0)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it("deduplicates variables from multiple sources", () => {
|
|
368
|
+
process.env.SHAKAPACKER_ENV_VARS = "NODE_ENV,CUSTOM_VAR"
|
|
369
|
+
|
|
370
|
+
const { getAllowedEnvVars } = getEnvFilter()
|
|
371
|
+
const allowed = getAllowedEnvVars()
|
|
372
|
+
|
|
373
|
+
// NODE_ENV should only appear once (from defaults, not duplicated from CSV)
|
|
374
|
+
const nodeEnvCount = allowed.filter((v) => v === "NODE_ENV").length
|
|
375
|
+
expect(nodeEnvCount).toBe(1)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
describe("getFilteredEnv", () => {
|
|
380
|
+
it("exposes allowed variables with their values", () => {
|
|
381
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
382
|
+
process.env.NODE_ENV = "production"
|
|
383
|
+
process.env.RAILS_ENV = "staging"
|
|
384
|
+
|
|
385
|
+
const { getFilteredEnv } = getEnvFilter()
|
|
386
|
+
const filtered = getFilteredEnv()
|
|
387
|
+
|
|
388
|
+
expect(filtered).toHaveProperty("NODE_ENV", "production")
|
|
389
|
+
expect(filtered).toHaveProperty("RAILS_ENV", "staging")
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it("omits sensitive variables that are not in allowlist", () => {
|
|
393
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
394
|
+
// Sensitive vars are set in beforeEach
|
|
395
|
+
|
|
396
|
+
const { getFilteredEnv } = getEnvFilter()
|
|
397
|
+
const filtered = getFilteredEnv()
|
|
398
|
+
|
|
399
|
+
// SECURITY: These must NOT be present
|
|
400
|
+
expect(filtered).not.toHaveProperty("DATABASE_URL")
|
|
401
|
+
expect(filtered).not.toHaveProperty("AWS_ACCESS_KEY_ID")
|
|
402
|
+
expect(filtered).not.toHaveProperty("AWS_SECRET_ACCESS_KEY")
|
|
403
|
+
expect(filtered).not.toHaveProperty("RAILS_MASTER_KEY")
|
|
404
|
+
expect(filtered).not.toHaveProperty("STRIPE_SECRET_KEY")
|
|
405
|
+
expect(filtered).not.toHaveProperty("SESSION_SECRET")
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it("exposes SHAKAPACKER_PUBLIC_* variables with their values", () => {
|
|
409
|
+
delete process.env.SHAKAPACKER_ENV_VARS
|
|
410
|
+
process.env.SHAKAPACKER_PUBLIC_API_URL = "https://api.example.com"
|
|
411
|
+
|
|
412
|
+
const { getFilteredEnv } = getEnvFilter()
|
|
413
|
+
const filtered = getFilteredEnv()
|
|
414
|
+
|
|
415
|
+
expect(filtered).toHaveProperty(
|
|
416
|
+
"SHAKAPACKER_PUBLIC_API_URL",
|
|
417
|
+
"https://api.example.com"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
// Cleanup
|
|
421
|
+
delete process.env.SHAKAPACKER_PUBLIC_API_URL
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it("uses null for missing variables from SHAKAPACKER_ENV_VARS", () => {
|
|
425
|
+
process.env.SHAKAPACKER_ENV_VARS = "MISSING_VAR,ANOTHER_MISSING"
|
|
426
|
+
delete process.env.MISSING_VAR
|
|
427
|
+
delete process.env.ANOTHER_MISSING
|
|
428
|
+
|
|
429
|
+
const { getFilteredEnv } = getEnvFilter()
|
|
430
|
+
const filtered = getFilteredEnv()
|
|
431
|
+
|
|
432
|
+
expect(filtered).toHaveProperty("MISSING_VAR", null)
|
|
433
|
+
expect(filtered).toHaveProperty("ANOTHER_MISSING", null)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it("includes variables from SHAKAPACKER_ENV_VARS with their values", () => {
|
|
437
|
+
process.env.SHAKAPACKER_ENV_VARS = "CUSTOM_API_URL"
|
|
438
|
+
process.env.CUSTOM_API_URL = "https://custom.example.com"
|
|
439
|
+
|
|
440
|
+
const { getFilteredEnv } = getEnvFilter()
|
|
441
|
+
const filtered = getFilteredEnv()
|
|
442
|
+
|
|
443
|
+
expect(filtered).toHaveProperty(
|
|
444
|
+
"CUSTOM_API_URL",
|
|
445
|
+
"https://custom.example.com"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
// Cleanup
|
|
449
|
+
delete process.env.CUSTOM_API_URL
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
})
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shakapacker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 9.
|
|
4
|
+
version: 9.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -174,7 +174,6 @@ files:
|
|
|
174
174
|
- ESLINT_TECHNICAL_DEBT.md
|
|
175
175
|
- Gemfile
|
|
176
176
|
- Gemfile.development_dependencies
|
|
177
|
-
- Gemfile.lock
|
|
178
177
|
- MIT-LICENSE
|
|
179
178
|
- README.md
|
|
180
179
|
- Rakefile
|
|
@@ -182,6 +181,7 @@ files:
|
|
|
182
181
|
- TODO_v9.md
|
|
183
182
|
- __mocks__/nonexistent/package.json
|
|
184
183
|
- __mocks__/sass-loader/package.json
|
|
184
|
+
- bin/shakapacker-config
|
|
185
185
|
- conductor-setup.sh
|
|
186
186
|
- conductor.json
|
|
187
187
|
- config/README.md
|
|
@@ -314,6 +314,7 @@ files:
|
|
|
314
314
|
- package/loaders.d.ts
|
|
315
315
|
- package/optimization/rspack.ts
|
|
316
316
|
- package/optimization/webpack.ts
|
|
317
|
+
- package/plugins/envFilter.ts
|
|
317
318
|
- package/plugins/rspack.ts
|
|
318
319
|
- package/plugins/webpack.ts
|
|
319
320
|
- package/rspack/index.ts
|
|
@@ -381,6 +382,7 @@ files:
|
|
|
381
382
|
- test/package/environments/production.test.js
|
|
382
383
|
- test/package/helpers.test.js
|
|
383
384
|
- test/package/index.test.js
|
|
385
|
+
- test/package/plugins/envFiltering.test.js
|
|
384
386
|
- test/package/production.test.js
|
|
385
387
|
- test/package/rules/babel.test.js
|
|
386
388
|
- test/package/rules/esbuild.test.js
|
|
@@ -413,7 +415,7 @@ homepage: https://github.com/shakacode/shakapacker
|
|
|
413
415
|
licenses:
|
|
414
416
|
- MIT
|
|
415
417
|
metadata:
|
|
416
|
-
source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.
|
|
418
|
+
source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.5.0
|
|
417
419
|
rdoc_options: []
|
|
418
420
|
require_paths:
|
|
419
421
|
- lib
|
|
@@ -448,6 +450,7 @@ test_files:
|
|
|
448
450
|
- test/package/environments/production.test.js
|
|
449
451
|
- test/package/helpers.test.js
|
|
450
452
|
- test/package/index.test.js
|
|
453
|
+
- test/package/plugins/envFiltering.test.js
|
|
451
454
|
- test/package/production.test.js
|
|
452
455
|
- test/package/rules/babel.test.js
|
|
453
456
|
- test/package/rules/esbuild.test.js
|
data/Gemfile.lock
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
shakapacker (9.4.0)
|
|
5
|
-
activesupport (>= 5.2)
|
|
6
|
-
package_json
|
|
7
|
-
rack-proxy (>= 0.6.1)
|
|
8
|
-
railties (>= 5.2)
|
|
9
|
-
semantic_range (>= 2.3.0)
|
|
10
|
-
|
|
11
|
-
GEM
|
|
12
|
-
remote: https://rubygems.org/
|
|
13
|
-
specs:
|
|
14
|
-
actioncable (7.2.1)
|
|
15
|
-
actionpack (= 7.2.1)
|
|
16
|
-
activesupport (= 7.2.1)
|
|
17
|
-
nio4r (~> 2.0)
|
|
18
|
-
websocket-driver (>= 0.6.1)
|
|
19
|
-
zeitwerk (~> 2.6)
|
|
20
|
-
actionmailbox (7.2.1)
|
|
21
|
-
actionpack (= 7.2.1)
|
|
22
|
-
activejob (= 7.2.1)
|
|
23
|
-
activerecord (= 7.2.1)
|
|
24
|
-
activestorage (= 7.2.1)
|
|
25
|
-
activesupport (= 7.2.1)
|
|
26
|
-
mail (>= 2.8.0)
|
|
27
|
-
actionmailer (7.2.1)
|
|
28
|
-
actionpack (= 7.2.1)
|
|
29
|
-
actionview (= 7.2.1)
|
|
30
|
-
activejob (= 7.2.1)
|
|
31
|
-
activesupport (= 7.2.1)
|
|
32
|
-
mail (>= 2.8.0)
|
|
33
|
-
rails-dom-testing (~> 2.2)
|
|
34
|
-
actionpack (7.2.1)
|
|
35
|
-
actionview (= 7.2.1)
|
|
36
|
-
activesupport (= 7.2.1)
|
|
37
|
-
nokogiri (>= 1.8.5)
|
|
38
|
-
racc
|
|
39
|
-
rack (>= 2.2.4, < 3.2)
|
|
40
|
-
rack-session (>= 1.0.1)
|
|
41
|
-
rack-test (>= 0.6.3)
|
|
42
|
-
rails-dom-testing (~> 2.2)
|
|
43
|
-
rails-html-sanitizer (~> 1.6)
|
|
44
|
-
useragent (~> 0.16)
|
|
45
|
-
actiontext (7.2.1)
|
|
46
|
-
actionpack (= 7.2.1)
|
|
47
|
-
activerecord (= 7.2.1)
|
|
48
|
-
activestorage (= 7.2.1)
|
|
49
|
-
activesupport (= 7.2.1)
|
|
50
|
-
globalid (>= 0.6.0)
|
|
51
|
-
nokogiri (>= 1.8.5)
|
|
52
|
-
actionview (7.2.1)
|
|
53
|
-
activesupport (= 7.2.1)
|
|
54
|
-
builder (~> 3.1)
|
|
55
|
-
erubi (~> 1.11)
|
|
56
|
-
rails-dom-testing (~> 2.2)
|
|
57
|
-
rails-html-sanitizer (~> 1.6)
|
|
58
|
-
activejob (7.2.1)
|
|
59
|
-
activesupport (= 7.2.1)
|
|
60
|
-
globalid (>= 0.3.6)
|
|
61
|
-
activemodel (7.2.1)
|
|
62
|
-
activesupport (= 7.2.1)
|
|
63
|
-
activerecord (7.2.1)
|
|
64
|
-
activemodel (= 7.2.1)
|
|
65
|
-
activesupport (= 7.2.1)
|
|
66
|
-
timeout (>= 0.4.0)
|
|
67
|
-
activestorage (7.2.1)
|
|
68
|
-
actionpack (= 7.2.1)
|
|
69
|
-
activejob (= 7.2.1)
|
|
70
|
-
activerecord (= 7.2.1)
|
|
71
|
-
activesupport (= 7.2.1)
|
|
72
|
-
marcel (~> 1.0)
|
|
73
|
-
activesupport (7.2.1)
|
|
74
|
-
base64
|
|
75
|
-
bigdecimal
|
|
76
|
-
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
77
|
-
connection_pool (>= 2.2.5)
|
|
78
|
-
drb
|
|
79
|
-
i18n (>= 1.6, < 2)
|
|
80
|
-
logger (>= 1.4.2)
|
|
81
|
-
minitest (>= 5.1)
|
|
82
|
-
securerandom (>= 0.3)
|
|
83
|
-
tzinfo (~> 2.0, >= 2.0.5)
|
|
84
|
-
ast (2.4.2)
|
|
85
|
-
base64 (0.2.0)
|
|
86
|
-
bigdecimal (3.1.8)
|
|
87
|
-
builder (3.3.0)
|
|
88
|
-
byebug (11.1.3)
|
|
89
|
-
concurrent-ruby (1.3.4)
|
|
90
|
-
connection_pool (2.4.1)
|
|
91
|
-
crass (1.0.6)
|
|
92
|
-
date (3.3.4)
|
|
93
|
-
diff-lcs (1.5.1)
|
|
94
|
-
drb (2.2.1)
|
|
95
|
-
erubi (1.13.0)
|
|
96
|
-
globalid (1.2.1)
|
|
97
|
-
activesupport (>= 6.1)
|
|
98
|
-
i18n (1.14.5)
|
|
99
|
-
concurrent-ruby (~> 1.0)
|
|
100
|
-
io-console (0.7.2)
|
|
101
|
-
irb (1.14.0)
|
|
102
|
-
rdoc (>= 4.0.0)
|
|
103
|
-
reline (>= 0.4.2)
|
|
104
|
-
json (2.7.2)
|
|
105
|
-
language_server-protocol (3.17.0.3)
|
|
106
|
-
logger (1.6.0)
|
|
107
|
-
loofah (2.22.0)
|
|
108
|
-
crass (~> 1.0.2)
|
|
109
|
-
nokogiri (>= 1.12.0)
|
|
110
|
-
mail (2.8.1)
|
|
111
|
-
mini_mime (>= 0.1.1)
|
|
112
|
-
net-imap
|
|
113
|
-
net-pop
|
|
114
|
-
net-smtp
|
|
115
|
-
marcel (1.0.4)
|
|
116
|
-
mini_mime (1.1.5)
|
|
117
|
-
mini_portile2 (2.8.7)
|
|
118
|
-
minitest (5.25.1)
|
|
119
|
-
net-imap (0.4.15)
|
|
120
|
-
date
|
|
121
|
-
net-protocol
|
|
122
|
-
net-pop (0.1.2)
|
|
123
|
-
net-protocol
|
|
124
|
-
net-protocol (0.2.2)
|
|
125
|
-
timeout
|
|
126
|
-
net-smtp (0.5.0)
|
|
127
|
-
net-protocol
|
|
128
|
-
nio4r (2.7.3)
|
|
129
|
-
nokogiri (1.16.7)
|
|
130
|
-
mini_portile2 (~> 2.8.2)
|
|
131
|
-
racc (~> 1.4)
|
|
132
|
-
package_json (0.1.0)
|
|
133
|
-
parallel (1.26.3)
|
|
134
|
-
parser (3.3.4.2)
|
|
135
|
-
ast (~> 2.4.1)
|
|
136
|
-
racc
|
|
137
|
-
psych (5.1.2)
|
|
138
|
-
stringio
|
|
139
|
-
racc (1.8.1)
|
|
140
|
-
rack (3.1.7)
|
|
141
|
-
rack-proxy (0.7.7)
|
|
142
|
-
rack
|
|
143
|
-
rack-session (2.0.0)
|
|
144
|
-
rack (>= 3.0.0)
|
|
145
|
-
rack-test (2.1.0)
|
|
146
|
-
rack (>= 1.3)
|
|
147
|
-
rackup (2.1.0)
|
|
148
|
-
rack (>= 3)
|
|
149
|
-
webrick (~> 1.8)
|
|
150
|
-
rails (7.2.1)
|
|
151
|
-
actioncable (= 7.2.1)
|
|
152
|
-
actionmailbox (= 7.2.1)
|
|
153
|
-
actionmailer (= 7.2.1)
|
|
154
|
-
actionpack (= 7.2.1)
|
|
155
|
-
actiontext (= 7.2.1)
|
|
156
|
-
actionview (= 7.2.1)
|
|
157
|
-
activejob (= 7.2.1)
|
|
158
|
-
activemodel (= 7.2.1)
|
|
159
|
-
activerecord (= 7.2.1)
|
|
160
|
-
activestorage (= 7.2.1)
|
|
161
|
-
activesupport (= 7.2.1)
|
|
162
|
-
bundler (>= 1.15.0)
|
|
163
|
-
railties (= 7.2.1)
|
|
164
|
-
rails-dom-testing (2.2.0)
|
|
165
|
-
activesupport (>= 5.0.0)
|
|
166
|
-
minitest
|
|
167
|
-
nokogiri (>= 1.6)
|
|
168
|
-
rails-html-sanitizer (1.6.0)
|
|
169
|
-
loofah (~> 2.21)
|
|
170
|
-
nokogiri (~> 1.14)
|
|
171
|
-
railties (7.2.1)
|
|
172
|
-
actionpack (= 7.2.1)
|
|
173
|
-
activesupport (= 7.2.1)
|
|
174
|
-
irb (~> 1.13)
|
|
175
|
-
rackup (>= 1.0.0)
|
|
176
|
-
rake (>= 12.2)
|
|
177
|
-
thor (~> 1.0, >= 1.2.2)
|
|
178
|
-
zeitwerk (~> 2.6)
|
|
179
|
-
rainbow (3.1.1)
|
|
180
|
-
rake (13.2.1)
|
|
181
|
-
rbs (3.9.5)
|
|
182
|
-
logger
|
|
183
|
-
rdoc (6.7.0)
|
|
184
|
-
psych (>= 4.0.0)
|
|
185
|
-
regexp_parser (2.9.2)
|
|
186
|
-
reline (0.5.9)
|
|
187
|
-
io-console (~> 0.5)
|
|
188
|
-
rspec-core (3.13.0)
|
|
189
|
-
rspec-support (~> 3.13.0)
|
|
190
|
-
rspec-expectations (3.13.2)
|
|
191
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
192
|
-
rspec-support (~> 3.13.0)
|
|
193
|
-
rspec-mocks (3.13.1)
|
|
194
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
195
|
-
rspec-support (~> 3.13.0)
|
|
196
|
-
rspec-rails (6.0.4)
|
|
197
|
-
actionpack (>= 6.1)
|
|
198
|
-
activesupport (>= 6.1)
|
|
199
|
-
railties (>= 6.1)
|
|
200
|
-
rspec-core (~> 3.12)
|
|
201
|
-
rspec-expectations (~> 3.12)
|
|
202
|
-
rspec-mocks (~> 3.12)
|
|
203
|
-
rspec-support (~> 3.12)
|
|
204
|
-
rspec-support (3.13.1)
|
|
205
|
-
rubocop (1.66.0)
|
|
206
|
-
json (~> 2.3)
|
|
207
|
-
language_server-protocol (>= 3.17.0)
|
|
208
|
-
parallel (~> 1.10)
|
|
209
|
-
parser (>= 3.3.0.2)
|
|
210
|
-
rainbow (>= 2.2.2, < 4.0)
|
|
211
|
-
regexp_parser (>= 2.4, < 3.0)
|
|
212
|
-
rubocop-ast (>= 1.32.1, < 2.0)
|
|
213
|
-
ruby-progressbar (~> 1.7)
|
|
214
|
-
unicode-display_width (>= 2.4.0, < 3.0)
|
|
215
|
-
rubocop-ast (1.32.1)
|
|
216
|
-
parser (>= 3.3.1.0)
|
|
217
|
-
rubocop-performance (1.21.1)
|
|
218
|
-
rubocop (>= 1.48.1, < 2.0)
|
|
219
|
-
rubocop-ast (>= 1.31.1, < 2.0)
|
|
220
|
-
ruby-progressbar (1.13.0)
|
|
221
|
-
securerandom (0.3.1)
|
|
222
|
-
semantic_range (3.1.0)
|
|
223
|
-
stringio (3.1.1)
|
|
224
|
-
thor (1.3.2)
|
|
225
|
-
timeout (0.4.1)
|
|
226
|
-
tzinfo (2.0.6)
|
|
227
|
-
concurrent-ruby (~> 1.0)
|
|
228
|
-
unicode-display_width (2.5.0)
|
|
229
|
-
useragent (0.16.10)
|
|
230
|
-
webrick (1.8.1)
|
|
231
|
-
websocket-driver (0.7.6)
|
|
232
|
-
websocket-extensions (>= 0.1.0)
|
|
233
|
-
websocket-extensions (0.1.5)
|
|
234
|
-
zeitwerk (2.6.17)
|
|
235
|
-
|
|
236
|
-
PLATFORMS
|
|
237
|
-
ruby
|
|
238
|
-
|
|
239
|
-
DEPENDENCIES
|
|
240
|
-
bundler (>= 1.3.0)
|
|
241
|
-
byebug
|
|
242
|
-
nokogiri (>= 1.13.6)
|
|
243
|
-
rack-proxy (>= 0.7.2)
|
|
244
|
-
rails (>= 6.1.6)
|
|
245
|
-
rails-html-sanitizer (>= 1.4.3)
|
|
246
|
-
rake (>= 11.1)
|
|
247
|
-
rbs (~> 3.0)
|
|
248
|
-
rspec-rails (~> 6.0.2)
|
|
249
|
-
rubocop
|
|
250
|
-
rubocop-performance
|
|
251
|
-
shakapacker!
|
|
252
|
-
|
|
253
|
-
BUNDLED WITH
|
|
254
|
-
2.5.23
|