-
Notifications
You must be signed in to change notification settings - Fork 12
[APPS-2792] Add: local-file resolution for Custom Credentials #512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tyffical
wants to merge
8
commits into
master
from
tiffany.trinh/apps-2792-custom-credentials-local-resolution
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
fce1e71
feat(apps): resolve Custom Credentials from a local override file
tyffical bc19d83
fix(apps): stop Custom Credentials leaking into JSON parse-error mess…
tyffical 5f68f08
fix(apps): deny dev-server access to the local Custom Credentials file
tyffical 47688c4
fix(apps): never package the local Custom Credentials file into a shi…
tyffical 37aad0e
fix(apps): reject direct imports of the local Custom Credentials file
tyffical 043d26a
fix(apps): strip resource query before matching the Custom Credential…
tyffical c22066f
fix(apps): close hash-suffix and symlink/hardlink bypasses of the Cus…
tyffical 471bd91
fix(apps): resolve real Custom Credentials for the priming/cold-load …
tyffical File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
packages/plugins/apps/src/vite/custom-credentials-resolver.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| // Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. | ||
| // This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| // Copyright 2019-Present Datadog, Inc. | ||
|
|
||
| import fs from 'node:fs/promises'; | ||
| import os from 'node:os'; | ||
| import path from 'node:path'; | ||
|
|
||
| import { | ||
| CUSTOM_CREDENTIALS_LOCAL_FILENAME, | ||
| resolveCustomCredentials, | ||
| } from './custom-credentials-resolver'; | ||
|
|
||
| describe('resolveCustomCredentials', () => { | ||
| let projectRoot: string; | ||
|
|
||
| beforeEach(async () => { | ||
| projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'custom-credentials-resolver-')); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await fs.rm(projectRoot, { recursive: true, force: true }); | ||
| }); | ||
|
|
||
| it('resolves to {} when the file does not exist', async () => { | ||
| await expect(resolveCustomCredentials(projectRoot)).resolves.toEqual({}); | ||
| }); | ||
|
|
||
| it('resolves the flat object of env var name to value', async () => { | ||
| await fs.writeFile( | ||
| path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME), | ||
| JSON.stringify({ STRIPE_API_KEY: 'sk_test_123' }), | ||
| ); | ||
|
|
||
| await expect(resolveCustomCredentials(projectRoot)).resolves.toEqual({ | ||
| STRIPE_API_KEY: 'sk_test_123', | ||
| }); | ||
| }); | ||
|
|
||
| it('rejects malformed JSON instead of silently returning {}', async () => { | ||
| await fs.writeFile( | ||
| path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME), | ||
| '{ not valid json', | ||
| ); | ||
|
|
||
| await expect(resolveCustomCredentials(projectRoot)).rejects.toThrow(/not valid JSON/); | ||
| }); | ||
|
|
||
| it('never echoes a real secret value into the parse-error message', async () => { | ||
| // An unquoted JSON value triggers V8's parse error to embed a source-text slice — the | ||
| // real bug this guards against. Deliberately not shaped like a real credential (no | ||
| // digits, no known prefix) so this fixture doesn't trip secret-scanning on push. | ||
| const secret = 'THIS_TOKEN_MUST_NEVER_LEAK_INTO_ANY_ERROR_MESSAGE'; | ||
| await fs.writeFile( | ||
| path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME), | ||
| `{"STRIPE_API_KEY": ${secret}}`, | ||
| ); | ||
|
|
||
| let thrown: unknown; | ||
| try { | ||
| await resolveCustomCredentials(projectRoot); | ||
| } catch (error) { | ||
| thrown = error; | ||
| } | ||
|
|
||
| expect(thrown).toBeInstanceOf(Error); | ||
| if (!(thrown instanceof Error)) { | ||
| throw thrown; | ||
| } | ||
| expect(thrown.message).not.toContain(secret); | ||
| expect(thrown.message).not.toContain(secret.slice(0, 10)); | ||
| }); | ||
|
|
||
| it('rejects a top-level array', async () => { | ||
| await fs.writeFile( | ||
| path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME), | ||
| JSON.stringify(['STRIPE_API_KEY']), | ||
| ); | ||
|
|
||
| await expect(resolveCustomCredentials(projectRoot)).rejects.toThrow(/flat JSON object/); | ||
| }); | ||
|
|
||
| it('rejects a non-string value, naming the offending key', async () => { | ||
| await fs.writeFile( | ||
| path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME), | ||
| JSON.stringify({ STRIPE_API_KEY: 12345 }), | ||
| ); | ||
|
|
||
| await expect(resolveCustomCredentials(projectRoot)).rejects.toThrow( | ||
| /"STRIPE_API_KEY".*must be a string/, | ||
| ); | ||
| }); | ||
|
|
||
| it('resolves a credential literally named "__proto__" instead of silently dropping it', async () => { | ||
| // Written as a raw string, not JSON.stringify({...}): object-literal `__proto__` syntax | ||
| // special-cases to set the prototype rather than create an own property, so stringifying | ||
| // it would silently produce {} here — JSON.parse has no such special case. | ||
| await fs.writeFile( | ||
| path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME), | ||
| '{"__proto__": "sk_test_proto", "STRIPE_API_KEY": "sk_test_123"}', | ||
| ); | ||
|
|
||
| // Bracket access via a variable key, not `resolved.__proto__`, since the latter triggers | ||
| // eslint's no-proto rule even though this is reading an ordinary data property here. | ||
| const protoKey = '__proto__'; | ||
| const resolved = await resolveCustomCredentials(projectRoot); | ||
| expect(Object.prototype.hasOwnProperty.call(resolved, protoKey)).toBe(true); | ||
| expect(resolved[protoKey]).toBe('sk_test_proto'); | ||
| expect(resolved.STRIPE_API_KEY).toBe('sk_test_123'); | ||
| }); | ||
|
|
||
| it('propagates a non-ENOENT filesystem error instead of treating it as "missing"', async () => { | ||
| // A directory where a file is expected fails to read with EISDIR, not ENOENT — resolving | ||
| // to {} here would hide a real misconfiguration (e.g. a stray directory shadowing the file). | ||
| await fs.mkdir(path.join(projectRoot, CUSTOM_CREDENTIALS_LOCAL_FILENAME)); | ||
|
|
||
| await expect(resolveCustomCredentials(projectRoot)).rejects.toThrow(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.