@superblocksteam/shared 0.9608.3 → 0.9609.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.
- package/dist/types/application/index.d.ts +8 -0
- package/dist/types/application/index.d.ts.map +1 -1
- package/dist/types/application/index.js.map +1 -1
- package/dist/types/audit/ocsf.d.ts.map +1 -1
- package/dist/types/audit/ocsf.js +3 -1
- package/dist/types/audit/ocsf.js.map +1 -1
- package/dist/types/datasource/auth.d.ts +1 -0
- package/dist/types/datasource/auth.d.ts.map +1 -1
- package/dist/types/datasource/auth.js +26 -0
- package/dist/types/datasource/auth.js.map +1 -1
- package/dist/types/datasource/auth.test.js +57 -0
- package/dist/types/datasource/auth.test.js.map +1 -1
- package/dist/types/policyGate/index.d.ts +29 -0
- package/dist/types/policyGate/index.d.ts.map +1 -1
- package/dist/types/policyGate/index.js.map +1 -1
- package/dist/types/response/DeleteApplicationResponseBody.d.ts +2 -2
- package/dist/types/response/DeleteApplicationResponseBody.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/launchDarkly.d.ts +18 -0
- package/dist/utils/launchDarkly.d.ts.map +1 -0
- package/dist/utils/launchDarkly.js +21 -0
- package/dist/utils/launchDarkly.js.map +1 -0
- package/dist/utils/launchDarkly.test.d.ts +2 -0
- package/dist/utils/launchDarkly.test.d.ts.map +1 -0
- package/dist/utils/launchDarkly.test.js +24 -0
- package/dist/utils/launchDarkly.test.js.map +1 -0
- package/dist-esm/types/application/index.d.ts +8 -0
- package/dist-esm/types/application/index.d.ts.map +1 -1
- package/dist-esm/types/application/index.js.map +1 -1
- package/dist-esm/types/audit/ocsf.d.ts.map +1 -1
- package/dist-esm/types/audit/ocsf.js +3 -1
- package/dist-esm/types/audit/ocsf.js.map +1 -1
- package/dist-esm/types/datasource/auth.d.ts +1 -0
- package/dist-esm/types/datasource/auth.d.ts.map +1 -1
- package/dist-esm/types/datasource/auth.js +25 -0
- package/dist-esm/types/datasource/auth.js.map +1 -1
- package/dist-esm/types/datasource/auth.test.js +58 -1
- package/dist-esm/types/datasource/auth.test.js.map +1 -1
- package/dist-esm/types/policyGate/index.d.ts +29 -0
- package/dist-esm/types/policyGate/index.d.ts.map +1 -1
- package/dist-esm/types/policyGate/index.js.map +1 -1
- package/dist-esm/types/response/DeleteApplicationResponseBody.d.ts +2 -2
- package/dist-esm/types/response/DeleteApplicationResponseBody.d.ts.map +1 -1
- package/dist-esm/utils/index.d.ts +1 -0
- package/dist-esm/utils/index.d.ts.map +1 -1
- package/dist-esm/utils/index.js +1 -0
- package/dist-esm/utils/index.js.map +1 -1
- package/dist-esm/utils/launchDarkly.d.ts +18 -0
- package/dist-esm/utils/launchDarkly.d.ts.map +1 -0
- package/dist-esm/utils/launchDarkly.js +18 -0
- package/dist-esm/utils/launchDarkly.js.map +1 -0
- package/dist-esm/utils/launchDarkly.test.d.ts +2 -0
- package/dist-esm/utils/launchDarkly.test.d.ts.map +1 -0
- package/dist-esm/utils/launchDarkly.test.js +22 -0
- package/dist-esm/utils/launchDarkly.test.js.map +1 -0
- package/package.json +1 -1
- package/src/types/application/index.ts +11 -0
- package/src/types/audit/ocsf.ts +3 -1
- package/src/types/datasource/auth.test.ts +80 -0
- package/src/types/datasource/auth.ts +32 -0
- package/src/types/policyGate/index.ts +34 -0
- package/src/types/response/DeleteApplicationResponseBody.ts +2 -2
- package/src/utils/index.ts +1 -0
- package/src/utils/launchDarkly.test.ts +25 -0
- package/src/utils/launchDarkly.ts +23 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics events drive LaunchDarkly's per-context billing: monthly active users are
|
|
3
|
+
* counted from the unique context keys LaunchDarkly receives events for. Throwaway
|
|
4
|
+
* contexts, such as the per-run organizations created by end-to-end tests, each bill as
|
|
5
|
+
* an additional monthly active user, so events should be off outside real deployments.
|
|
6
|
+
*
|
|
7
|
+
* Events are on by default and only an explicit 'false' turns them off, matching
|
|
8
|
+
* SUPERBLOCKS_SERVER_LAUNCH_DARKLY_SEND_EVENTS_ENABLED on the server-side SDK. Any
|
|
9
|
+
* unrecognized value keeps events on so a malformed setting cannot silently disable
|
|
10
|
+
* reporting for a real deployment.
|
|
11
|
+
*/
|
|
12
|
+
export function shouldSendLaunchDarklyEvents(value) {
|
|
13
|
+
if (typeof value === 'boolean') {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return value?.trim().toLowerCase() !== 'false';
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=launchDarkly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchDarkly.js","sourceRoot":"","sources":["../../src/utils/launchDarkly.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAAoC;IAC/E,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchDarkly.test.d.ts","sourceRoot":"","sources":["../../src/utils/launchDarkly.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { shouldSendLaunchDarklyEvents } from './launchDarkly.js';
|
|
3
|
+
describe('shouldSendLaunchDarklyEvents', () => {
|
|
4
|
+
test('sends events by default when unset', () => {
|
|
5
|
+
expect(shouldSendLaunchDarklyEvents(undefined)).toBe(true);
|
|
6
|
+
expect(shouldSendLaunchDarklyEvents(null)).toBe(true);
|
|
7
|
+
expect(shouldSendLaunchDarklyEvents('')).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
test('only an explicit false disables events', () => {
|
|
10
|
+
expect(shouldSendLaunchDarklyEvents('false')).toBe(false);
|
|
11
|
+
expect(shouldSendLaunchDarklyEvents('FALSE')).toBe(false);
|
|
12
|
+
expect(shouldSendLaunchDarklyEvents(' false ')).toBe(false);
|
|
13
|
+
expect(shouldSendLaunchDarklyEvents(false)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
test('sends events for truthy and unrecognized values', () => {
|
|
16
|
+
expect(shouldSendLaunchDarklyEvents('true')).toBe(true);
|
|
17
|
+
expect(shouldSendLaunchDarklyEvents(true)).toBe(true);
|
|
18
|
+
expect(shouldSendLaunchDarklyEvents('0')).toBe(true);
|
|
19
|
+
expect(shouldSendLaunchDarklyEvents('no')).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=launchDarkly.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchDarkly.test.js","sourceRoot":"","sources":["../../src/utils/launchDarkly.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEjE,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,IAAI,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,CAAC,4BAA4B,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,4BAA4B,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -472,6 +472,17 @@ export interface IApplicationV2Dto {
|
|
|
472
472
|
removedProfiles?: Record<string, Array<{ id: string; key: string; displayName: string }>>;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
export type DatabaseDisposition = 'delete' | 'retain';
|
|
476
|
+
|
|
477
|
+
export type DatabaseRetirementEnqueueFailure = {
|
|
478
|
+
applicationId: string;
|
|
479
|
+
bindingKey: string;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
export type DatabaseRetirementEnqueueResult = {
|
|
483
|
+
databaseRetirementEnqueueFailures: DatabaseRetirementEnqueueFailure[];
|
|
484
|
+
};
|
|
485
|
+
|
|
475
486
|
export interface IHomepageApplicationV2Dto extends IResourceMetadata {
|
|
476
487
|
deployedAt?: Date | null;
|
|
477
488
|
creator?: {
|
package/src/types/audit/ocsf.ts
CHANGED
|
@@ -289,7 +289,9 @@ export const AUDIT_EVENT_TYPE_CATALOG: AuditEventTypeEntry[] = [
|
|
|
289
289
|
'reviewpolicy.allowlist.applications.added',
|
|
290
290
|
'reviewpolicy.allowlist.applications.removed',
|
|
291
291
|
'reviewpolicy.create',
|
|
292
|
-
'reviewpolicy.delete'
|
|
292
|
+
'reviewpolicy.delete',
|
|
293
|
+
'reviewpolicy.disable',
|
|
294
|
+
'reviewpolicy.enable'
|
|
293
295
|
]
|
|
294
296
|
},
|
|
295
297
|
{
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
TokenScope,
|
|
6
6
|
extractPublic,
|
|
7
7
|
getAuthId,
|
|
8
|
+
getAuthIdCandidates,
|
|
8
9
|
getDisplayName,
|
|
9
10
|
getOAuthCodeVerifierStorageKey,
|
|
10
11
|
isPkceEnabled
|
|
@@ -229,6 +230,85 @@ describe('getAuthId normalizes boolean tokenScope in getClientId', () => {
|
|
|
229
230
|
});
|
|
230
231
|
});
|
|
231
232
|
|
|
233
|
+
describe('getAuthIdCandidates', () => {
|
|
234
|
+
test('user-scoped config also probes the org-unique configId keying (recognizes a datasource->user flip)', () => {
|
|
235
|
+
const authConfig = { clientId: 'my-client', tokenScope: TokenScope.USER, scope: 'read' };
|
|
236
|
+
const candidates = getAuthIdCandidates(IntegrationAuthType.OAUTH2_CODE, authConfig, 'int-1', 'cfg-1');
|
|
237
|
+
// The config's own current keying (user -> client id) PLUS the widened datasource keying (config id).
|
|
238
|
+
expect(candidates).toContain(getAuthId(IntegrationAuthType.OAUTH2_CODE, authConfig, 'int-1', 'cfg-1'));
|
|
239
|
+
expect(candidates).toContain(
|
|
240
|
+
getAuthId(
|
|
241
|
+
IntegrationAuthType.OAUTH2_CODE,
|
|
242
|
+
{ clientId: 'my-client', tokenScope: TokenScope.DATASOURCE, scope: 'read' },
|
|
243
|
+
'int-1',
|
|
244
|
+
'cfg-1'
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
expect(candidates).toHaveLength(2);
|
|
248
|
+
expect(candidates.some((c) => c.startsWith('oauth-code.cfg-1'))).toBe(true); // org-unique config id
|
|
249
|
+
expect(candidates.some((c) => c.startsWith('oauth-code.my-client'))).toBe(true); // its own current client-id key
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('datasource-scoped config widens ONLY to the configId keying, never synthesizes the client-id keying (SEC-73)', () => {
|
|
253
|
+
// The client-id keying is shared across tenants (many orgs can share a clientId + scope); synthesizing
|
|
254
|
+
// it for a datasource-scoped config would let one org witness ownership of another org's client-id-keyed
|
|
255
|
+
// shared token. It must never appear as a candidate for a datasource-scoped config.
|
|
256
|
+
const authConfig = { clientId: 'shared-client', tokenScope: TokenScope.DATASOURCE, scope: 'read' };
|
|
257
|
+
const candidates = getAuthIdCandidates(IntegrationAuthType.OAUTH2_CODE, authConfig, 'int-1', 'cfg-1');
|
|
258
|
+
expect(candidates).toEqual([getAuthId(IntegrationAuthType.OAUTH2_CODE, authConfig, 'int-1', 'cfg-1')]);
|
|
259
|
+
expect(candidates).toHaveLength(1);
|
|
260
|
+
expect(candidates[0]).toMatch(/^oauth-code\.cfg-1/); // org-unique config id
|
|
261
|
+
expect(candidates.some((c) => c.startsWith('oauth-code.shared-client'))).toBe(false); // no cross-tenant client-id key
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('collapses to one candidate when config id equals client id', () => {
|
|
265
|
+
const authConfig = { clientId: 'same', tokenScope: TokenScope.USER, scope: 'read' };
|
|
266
|
+
const candidates = getAuthIdCandidates(IntegrationAuthType.OAUTH2_CODE, authConfig, 'int-1', 'same');
|
|
267
|
+
expect(candidates).toHaveLength(1);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test.each([IntegrationAuthType.OAUTH2_CLIENT_CREDS, IntegrationAuthType.OAUTH2_IMPLICIT])(
|
|
271
|
+
'OAuth-split type %s (user-scoped) also probes the configId keying',
|
|
272
|
+
(authType) => {
|
|
273
|
+
const authConfig = { clientId: 'my-client', tokenScope: TokenScope.USER, scope: 'read' };
|
|
274
|
+
const candidates = getAuthIdCandidates(authType, authConfig, 'int-1', 'cfg-1');
|
|
275
|
+
expect(candidates).toHaveLength(2);
|
|
276
|
+
expect(candidates).toContain(
|
|
277
|
+
getAuthId(authType, { clientId: 'my-client', tokenScope: TokenScope.DATASOURCE, scope: 'read' }, 'int-1', 'cfg-1')
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
test.each([IntegrationAuthType.BASIC, IntegrationAuthType.FIREBASE, IntegrationAuthType.OAUTH2_PASSWORD])(
|
|
283
|
+
'scope-invariant type %s returns a single candidate (tokenScope does not affect keying)',
|
|
284
|
+
(authType) => {
|
|
285
|
+
const authConfig = { clientId: 'my-client', apiKey: '{"projectId":"p"}', scope: 'read' };
|
|
286
|
+
const candidates = getAuthIdCandidates(authType, authConfig, 'int-1', 'cfg-1');
|
|
287
|
+
expect(candidates).toHaveLength(1);
|
|
288
|
+
expect(candidates[0]).toBe(getAuthId(authType, authConfig, 'int-1', 'cfg-1'));
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
test('scope-invariant auth type returns a single candidate (tokenScope does not affect keying)', () => {
|
|
293
|
+
const authConfig = {
|
|
294
|
+
subjectTokenSource: 'SUBJECT_TOKEN_SOURCE_STATIC_TOKEN',
|
|
295
|
+
subjectTokenSourceStaticToken: 't',
|
|
296
|
+
tokenUrl: 'https://idp.example.com/token',
|
|
297
|
+
scope: 'read',
|
|
298
|
+
audience: 'aud',
|
|
299
|
+
clientId: 'c'
|
|
300
|
+
};
|
|
301
|
+
const candidates = getAuthIdCandidates(IntegrationAuthType.OAUTH2_TOKEN_EXCHANGE, authConfig, 'int-1', 'cfg-1');
|
|
302
|
+
expect(candidates).toHaveLength(1);
|
|
303
|
+
expect(candidates[0]).toBe(getAuthId(IntegrationAuthType.OAUTH2_TOKEN_EXCHANGE, authConfig, 'int-1', 'cfg-1'));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('undefined authConfig yields a single candidate', () => {
|
|
307
|
+
const candidates = getAuthIdCandidates(IntegrationAuthType.OAUTH2_CODE, undefined, 'int-1', 'cfg-1');
|
|
308
|
+
expect(candidates).toHaveLength(1);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
232
312
|
describe('getAuthId', () => {
|
|
233
313
|
test('uses unknown-client for OAUTH2_IDP_TOKEN_PASSTHROUGH (no client identity)', () => {
|
|
234
314
|
const authId = getAuthId(IntegrationAuthType.OAUTH2_IDP_TOKEN_PASSTHROUGH, {}, 'int-1', 'cfg-1');
|
|
@@ -60,6 +60,38 @@ export function getAuthId(
|
|
|
60
60
|
return `${authType}.${getClientId(authType, authConfig, integrationId, integrationConfigurationId)}`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// A token minted while a config's tokenScope was 'datasource' keys on the org-unique configuration id;
|
|
64
|
+
// after the config is flipped to 'user' a fresh recompute keys on the client id instead and no longer
|
|
65
|
+
// matches, false-denying the legitimate owner. getAuthIdCandidates returns the config's current-scope
|
|
66
|
+
// authId PLUS the datasource (configuration-id) keying, so an ownership recompute recognizes a token
|
|
67
|
+
// across a post-mint datasource->user flip.
|
|
68
|
+
//
|
|
69
|
+
// It widens ONLY toward the datasource keying, which is org-unique (the configuration id is a per-config
|
|
70
|
+
// UUID). It deliberately does NOT synthesize the user (client-id) keying for a datasource-scoped config:
|
|
71
|
+
// the client-id key is shared across tenants (multiple orgs can share a clientId + scope), so adding it
|
|
72
|
+
// would let one org's config witness ownership of another org's client-id-keyed shared token. Recognizing
|
|
73
|
+
// the reverse user->datasource flip would require matching that shared key, so it is intentionally not
|
|
74
|
+
// done here; closing it safely needs org-scoped token storage, tracked separately. See SEC-73.
|
|
75
|
+
export function getAuthIdCandidates(
|
|
76
|
+
authType: AuthType | undefined,
|
|
77
|
+
authConfig: AuthConfig | undefined,
|
|
78
|
+
integrationId?: string,
|
|
79
|
+
integrationConfigurationId?: string
|
|
80
|
+
): AuthId[] {
|
|
81
|
+
const current = getAuthId(authType, authConfig, integrationId, integrationConfigurationId);
|
|
82
|
+
const asDatasource = getAuthId(authType, withTokenScope(authConfig, TokenScope.DATASOURCE), integrationId, integrationConfigurationId);
|
|
83
|
+
return current === asDatasource ? [current] : [current, asDatasource];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Return a shallow copy of authConfig with tokenScope overridden, without mutating the caller's object.
|
|
87
|
+
// undefined passes through so getAuthId falls back to its unknown-client key.
|
|
88
|
+
function withTokenScope(authConfig: AuthConfig | undefined, tokenScope: TokenScope): AuthConfig | undefined {
|
|
89
|
+
if (!authConfig) {
|
|
90
|
+
return authConfig;
|
|
91
|
+
}
|
|
92
|
+
return { ...authConfig, tokenScope } as AuthConfig;
|
|
93
|
+
}
|
|
94
|
+
|
|
63
95
|
function getClientId(
|
|
64
96
|
authType: AuthType | undefined,
|
|
65
97
|
config: AuthConfig | undefined,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ReviewFindingCategory, ReviewFindingConfidence, ReviewFindingSeverity } from '../reviewPolicy/index.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @deprecated Use the `superblocks.policy-gates.security-scans.enabled` LaunchDarkly flag instead.
|
|
3
5
|
* Kept as `false` to match the fail-closed LD default; create and enable the
|
|
@@ -141,3 +143,35 @@ export const POLICY_GATE_BLOCKED_ERROR = 'POLICY_GATE_BLOCKED';
|
|
|
141
143
|
*/
|
|
142
144
|
export const POLICY_GATE_STALE_LIVE_EDIT_ERROR =
|
|
143
145
|
'Policy gates can only run for the current app state. Deploy the current changes or restore to this commit and then deploy.';
|
|
146
|
+
|
|
147
|
+
export type ScanScopeChangeType = 'added' | 'deleted' | 'modified';
|
|
148
|
+
|
|
149
|
+
export type ScanScopeChangedFile = {
|
|
150
|
+
changeType: ScanScopeChangeType;
|
|
151
|
+
path: string;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export type ScanScopePriorFinding = {
|
|
155
|
+
blocking: boolean;
|
|
156
|
+
category: ReviewFindingCategory;
|
|
157
|
+
clarkRemediable?: boolean;
|
|
158
|
+
confidence?: ReviewFindingConfidence | null;
|
|
159
|
+
evidenceJson?: Record<string, unknown>;
|
|
160
|
+
fingerprint: string;
|
|
161
|
+
humanSummary?: string | null;
|
|
162
|
+
locations: unknown[];
|
|
163
|
+
remediationHintJson?: Record<string, unknown>;
|
|
164
|
+
severity: ReviewFindingSeverity;
|
|
165
|
+
technicalSummary?: string | null;
|
|
166
|
+
title: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type ScanScopeJson = {
|
|
170
|
+
baseApplicationCommitId: string;
|
|
171
|
+
changedFiles: ScanScopeChangedFile[];
|
|
172
|
+
diffTruncated: boolean;
|
|
173
|
+
priorAiAgentRunId?: string;
|
|
174
|
+
priorFindings: ScanScopePriorFinding[];
|
|
175
|
+
priorReviewRunId?: string;
|
|
176
|
+
unifiedDiff: string;
|
|
177
|
+
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { IApplicationV2Dto } from '../application/index.js';
|
|
1
|
+
import type { DatabaseRetirementEnqueueResult, IApplicationV2Dto } from '../application/index.js';
|
|
2
2
|
|
|
3
|
-
export type DeleteApplicationResponseBody = IApplicationV2Dto
|
|
3
|
+
export type DeleteApplicationResponseBody = IApplicationV2Dto & Partial<DatabaseRetirementEnqueueResult>;
|
package/src/utils/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './configuration.js';
|
|
|
6
6
|
export * from './env.js';
|
|
7
7
|
export * from './environment.js';
|
|
8
8
|
export * from './git-url.js';
|
|
9
|
+
export * from './launchDarkly.js';
|
|
9
10
|
export * from './maskCredential.js';
|
|
10
11
|
export * from './normalize.js';
|
|
11
12
|
export * from './normalizeRoleName.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { shouldSendLaunchDarklyEvents } from './launchDarkly.js';
|
|
4
|
+
|
|
5
|
+
describe('shouldSendLaunchDarklyEvents', () => {
|
|
6
|
+
test('sends events by default when unset', () => {
|
|
7
|
+
expect(shouldSendLaunchDarklyEvents(undefined)).toBe(true);
|
|
8
|
+
expect(shouldSendLaunchDarklyEvents(null)).toBe(true);
|
|
9
|
+
expect(shouldSendLaunchDarklyEvents('')).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('only an explicit false disables events', () => {
|
|
13
|
+
expect(shouldSendLaunchDarklyEvents('false')).toBe(false);
|
|
14
|
+
expect(shouldSendLaunchDarklyEvents('FALSE')).toBe(false);
|
|
15
|
+
expect(shouldSendLaunchDarklyEvents(' false ')).toBe(false);
|
|
16
|
+
expect(shouldSendLaunchDarklyEvents(false)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('sends events for truthy and unrecognized values', () => {
|
|
20
|
+
expect(shouldSendLaunchDarklyEvents('true')).toBe(true);
|
|
21
|
+
expect(shouldSendLaunchDarklyEvents(true)).toBe(true);
|
|
22
|
+
expect(shouldSendLaunchDarklyEvents('0')).toBe(true);
|
|
23
|
+
expect(shouldSendLaunchDarklyEvents('no')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A LaunchDarkly send-events setting as it arrives from configuration: either an
|
|
3
|
+
* already-parsed boolean or the raw string form an env var carries.
|
|
4
|
+
*/
|
|
5
|
+
export type LaunchDarklySendEventsSetting = boolean | string | undefined | null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Analytics events drive LaunchDarkly's per-context billing: monthly active users are
|
|
9
|
+
* counted from the unique context keys LaunchDarkly receives events for. Throwaway
|
|
10
|
+
* contexts, such as the per-run organizations created by end-to-end tests, each bill as
|
|
11
|
+
* an additional monthly active user, so events should be off outside real deployments.
|
|
12
|
+
*
|
|
13
|
+
* Events are on by default and only an explicit 'false' turns them off, matching
|
|
14
|
+
* SUPERBLOCKS_SERVER_LAUNCH_DARKLY_SEND_EVENTS_ENABLED on the server-side SDK. Any
|
|
15
|
+
* unrecognized value keeps events on so a malformed setting cannot silently disable
|
|
16
|
+
* reporting for a real deployment.
|
|
17
|
+
*/
|
|
18
|
+
export function shouldSendLaunchDarklyEvents(value: LaunchDarklySendEventsSetting): boolean {
|
|
19
|
+
if (typeof value === 'boolean') {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
return value?.trim().toLowerCase() !== 'false';
|
|
23
|
+
}
|