mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 08:01:35 +00:00
fix: strip a secret under a container shaped unlike its declaration
This commit is contained in:
@@ -219,6 +219,23 @@ describe('secret args at every level the form nests', () => {
|
||||
expect(stripSecretArgs({ creds: [{ token: 'hunter2' }] }, both)).toEqual({ creds: [{}] })
|
||||
})
|
||||
|
||||
// A container shaped unlike its declaration is kept, so the walk has to reach in
|
||||
// through the half the declaration does carry — descending on the value's shape alone
|
||||
// left the secret sitting there for the persisted card and the model to read.
|
||||
it('strips through a container shaped unlike its declaration', () => {
|
||||
const declaresArray = {
|
||||
properties: {
|
||||
rows: { type: 'array', items: { properties: { token: { password: true } } } }
|
||||
}
|
||||
}
|
||||
expect(stripSecretArgs({ rows: { token: 'hunter2' } }, declaresArray)).toEqual({ rows: {} })
|
||||
|
||||
const declaresObject = {
|
||||
properties: { cfg: { type: 'object', properties: { token: { password: true } } } }
|
||||
}
|
||||
expect(stripSecretArgs({ cfg: [{ token: 'hunter2' }] }, declaresObject)).toEqual({ cfg: [{}] })
|
||||
})
|
||||
|
||||
// The caller binds the result to a form that edits in place, so a schema declaring
|
||||
// nothing must not hand back the object it was given.
|
||||
it('copies even when the schema declares nothing to strip', () => {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Preparing a caller's arguments for a run form, and for the readers of one. The filters
|
||||
* split by what a mistake costs: conforming must not drop what the user meant to send, so
|
||||
* it stays exact and shallow, while stripping and redacting only blank a field, so they go
|
||||
* to any depth and err towards visiting too much.
|
||||
*/
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
const isLockedProp = (prop: any) => !!prop?.disabled && 'default' in prop
|
||||
@@ -31,6 +37,13 @@ export function enforceDisabledDefaults(
|
||||
return { args: { ...result }, resetKeys }
|
||||
}
|
||||
|
||||
/** How a form says what {@link enforceDisabledDefaults} overwrote, shared by the two that
|
||||
* run it so the wording cannot drift apart. */
|
||||
export const resetKeysToast = (resetKeys: string[]): string =>
|
||||
`Disabled field${resetKeys.length > 1 ? 's' : ''} ${resetKeys
|
||||
.map((k) => `'${k}'`)
|
||||
.join(', ')} reset to default value${resetKeys.length > 1 ? 's' : ''}`
|
||||
|
||||
/**
|
||||
* Keys removed from a caller's arguments, split by cause. Kept apart because the two
|
||||
* read as opposites to whoever is told: an undeclared argument is one to stop sending,
|
||||
@@ -138,10 +151,16 @@ function mapLeaves(
|
||||
path: string
|
||||
): any {
|
||||
if (value == null || typeof value !== 'object') return value
|
||||
// A container shaped unlike its declaration is kept rather than dropped, since the widget
|
||||
// is the one that reports it — so the walk has to reach in through whichever half the
|
||||
// declaration does carry, or a secret under one leaves the form verbatim.
|
||||
if (Array.isArray(value))
|
||||
return value.map((item, i) => mapLeaves(item, prop?.items, isLeaf, visit, `${path}[${i}]`))
|
||||
return value.map((item, i) =>
|
||||
mapLeaves(item, prop?.items ?? prop, isLeaf, visit, `${path}[${i}]`)
|
||||
)
|
||||
const bags = declarationBags(prop)
|
||||
if (bags.length === 0) return value
|
||||
if (bags.length === 0)
|
||||
return prop?.items ? mapLeaves(value, prop.items, isLeaf, visit, path) : value
|
||||
// Null prototype, and keyed off the value rather than the declaration: a key is only
|
||||
// ever rewritten where it already exists, so no branch of a `oneOf` can add one.
|
||||
const result: Record<string, any> = Object.assign(Object.create(null), value)
|
||||
|
||||
Reference in New Issue
Block a user