@pipeline-builder/api-core 0.0.1
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/LICENSE +202 -0
- package/README.md +98 -0
- package/lib/constants/ai-providers.d.ts +52 -0
- package/lib/constants/ai-providers.js +129 -0
- package/lib/constants/http-status.d.ts +20 -0
- package/lib/constants/http-status.js +26 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.js +6 -0
- package/lib/constants/time.d.ts +10 -0
- package/lib/constants/time.js +13 -0
- package/lib/errors/app-errors.d.ts +30 -0
- package/lib/errors/app-errors.js +53 -0
- package/lib/errors/index.d.ts +1 -0
- package/lib/errors/index.js +4 -0
- package/lib/helpers/access-helpers.d.ts +63 -0
- package/lib/helpers/access-helpers.js +82 -0
- package/lib/helpers/crud-helpers.d.ts +9 -0
- package/lib/helpers/crud-helpers.js +23 -0
- package/lib/helpers/index.d.ts +6 -0
- package/lib/helpers/index.js +9 -0
- package/lib/helpers/org-hierarchy-http.d.ts +74 -0
- package/lib/helpers/org-hierarchy-http.js +144 -0
- package/lib/helpers/org-hierarchy.d.ts +57 -0
- package/lib/helpers/org-hierarchy.js +110 -0
- package/lib/helpers/restore-helpers.d.ts +49 -0
- package/lib/helpers/restore-helpers.js +78 -0
- package/lib/helpers/sse-helpers.d.ts +20 -0
- package/lib/helpers/sse-helpers.js +45 -0
- package/lib/index.d.ts +58 -0
- package/lib/index.js +71 -0
- package/lib/middleware/auth.d.ts +208 -0
- package/lib/middleware/auth.js +588 -0
- package/lib/middleware/index.d.ts +3 -0
- package/lib/middleware/index.js +6 -0
- package/lib/middleware/mongo-sanitize.d.ts +32 -0
- package/lib/middleware/mongo-sanitize.js +100 -0
- package/lib/middleware/step-up.d.ts +45 -0
- package/lib/middleware/step-up.js +138 -0
- package/lib/openapi/extend-zod.d.ts +1 -0
- package/lib/openapi/extend-zod.js +6 -0
- package/lib/openapi/index.d.ts +2 -0
- package/lib/openapi/index.js +5 -0
- package/lib/openapi/registry.d.ts +17 -0
- package/lib/openapi/registry.js +38 -0
- package/lib/openapi/routes/ask-routes.d.ts +7 -0
- package/lib/openapi/routes/ask-routes.js +62 -0
- package/lib/openapi/routes/billing-routes.d.ts +1 -0
- package/lib/openapi/routes/billing-routes.js +66 -0
- package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
- package/lib/openapi/routes/image-registry-routes.js +98 -0
- package/lib/openapi/routes/index.d.ts +5 -0
- package/lib/openapi/routes/index.js +28 -0
- package/lib/openapi/routes/message-routes.d.ts +1 -0
- package/lib/openapi/routes/message-routes.js +105 -0
- package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-routes.js +105 -0
- package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-template-routes.js +61 -0
- package/lib/openapi/routes/plugin-routes.d.ts +1 -0
- package/lib/openapi/routes/plugin-routes.js +96 -0
- package/lib/openapi/routes/quota-routes.d.ts +1 -0
- package/lib/openapi/routes/quota-routes.js +62 -0
- package/lib/openapi/schema-registry.d.ts +25 -0
- package/lib/openapi/schema-registry.js +91 -0
- package/lib/routes/health.d.ts +41 -0
- package/lib/routes/health.js +82 -0
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/index.js +4 -0
- package/lib/services/audit-spool.d.ts +80 -0
- package/lib/services/audit-spool.js +172 -0
- package/lib/services/cache-service.d.ts +124 -0
- package/lib/services/cache-service.js +276 -0
- package/lib/services/circuit-breaker.d.ts +45 -0
- package/lib/services/circuit-breaker.js +138 -0
- package/lib/services/compliance-client.d.ts +46 -0
- package/lib/services/compliance-client.js +128 -0
- package/lib/services/compliance-event-subscriber.d.ts +16 -0
- package/lib/services/compliance-event-subscriber.js +72 -0
- package/lib/services/durable-event-bus.d.ts +96 -0
- package/lib/services/durable-event-bus.js +204 -0
- package/lib/services/entity-events.d.ts +48 -0
- package/lib/services/entity-events.js +79 -0
- package/lib/services/env-redis.d.ts +44 -0
- package/lib/services/env-redis.js +94 -0
- package/lib/services/http-client.d.ts +143 -0
- package/lib/services/http-client.js +335 -0
- package/lib/services/index.d.ts +18 -0
- package/lib/services/index.js +20 -0
- package/lib/services/leader-lock.d.ts +45 -0
- package/lib/services/leader-lock.js +85 -0
- package/lib/services/quota.d.ts +130 -0
- package/lib/services/quota.js +264 -0
- package/lib/services/remote-audit-client.d.ts +160 -0
- package/lib/services/remote-audit-client.js +390 -0
- package/lib/services/retry-strategy.d.ts +74 -0
- package/lib/services/retry-strategy.js +118 -0
- package/lib/services/scheduler.d.ts +41 -0
- package/lib/services/scheduler.js +86 -0
- package/lib/services/service-boot.d.ts +20 -0
- package/lib/services/service-boot.js +28 -0
- package/lib/services/sse-ticket-store.d.ts +51 -0
- package/lib/services/sse-ticket-store.js +130 -0
- package/lib/services/token-revocation.d.ts +52 -0
- package/lib/services/token-revocation.js +120 -0
- package/lib/testing/tier-mock.d.ts +44 -0
- package/lib/testing/tier-mock.js +65 -0
- package/lib/types/audit-events.d.ts +69 -0
- package/lib/types/audit-events.js +4 -0
- package/lib/types/catalog-metadata.d.ts +36 -0
- package/lib/types/catalog-metadata.js +21 -0
- package/lib/types/common.d.ts +255 -0
- package/lib/types/common.js +41 -0
- package/lib/types/error-codes.d.ts +54 -0
- package/lib/types/error-codes.js +107 -0
- package/lib/types/feature-flags.d.ts +36 -0
- package/lib/types/feature-flags.js +128 -0
- package/lib/types/http.d.ts +27 -0
- package/lib/types/http.js +4 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +14 -0
- package/lib/types/permissions.d.ts +65 -0
- package/lib/types/permissions.js +131 -0
- package/lib/types/pipeline-template.d.ts +30 -0
- package/lib/types/pipeline-template.js +13 -0
- package/lib/types/pipeline.d.ts +59 -0
- package/lib/types/pipeline.js +31 -0
- package/lib/types/quota-tiers.d.ts +79 -0
- package/lib/types/quota-tiers.js +221 -0
- package/lib/types/visibility.d.ts +20 -0
- package/lib/types/visibility.js +22 -0
- package/lib/utils/alias-resolver.d.ts +37 -0
- package/lib/utils/alias-resolver.js +68 -0
- package/lib/utils/audit.d.ts +15 -0
- package/lib/utils/audit.js +39 -0
- package/lib/utils/aws-scrub.d.ts +11 -0
- package/lib/utils/aws-scrub.js +67 -0
- package/lib/utils/compliance-attributes.d.ts +6 -0
- package/lib/utils/compliance-attributes.js +69 -0
- package/lib/utils/concurrency.d.ts +9 -0
- package/lib/utils/concurrency.js +29 -0
- package/lib/utils/crash-handlers.d.ts +18 -0
- package/lib/utils/crash-handlers.js +36 -0
- package/lib/utils/env.d.ts +28 -0
- package/lib/utils/env.js +54 -0
- package/lib/utils/headers.d.ts +18 -0
- package/lib/utils/headers.js +21 -0
- package/lib/utils/identity.d.ts +40 -0
- package/lib/utils/identity.js +51 -0
- package/lib/utils/index.d.ts +17 -0
- package/lib/utils/index.js +20 -0
- package/lib/utils/logger.d.ts +38 -0
- package/lib/utils/logger.js +174 -0
- package/lib/utils/metric-emitter.d.ts +36 -0
- package/lib/utils/metric-emitter.js +32 -0
- package/lib/utils/object.d.ts +13 -0
- package/lib/utils/object.js +18 -0
- package/lib/utils/params.d.ts +150 -0
- package/lib/utils/params.js +206 -0
- package/lib/utils/response.d.ts +117 -0
- package/lib/utils/response.js +223 -0
- package/lib/utils/safe-require.d.ts +13 -0
- package/lib/utils/safe-require.js +19 -0
- package/lib/utils/secret-encryption.d.ts +176 -0
- package/lib/utils/secret-encryption.js +374 -0
- package/lib/utils/ssrf.d.ts +30 -0
- package/lib/utils/ssrf.js +106 -0
- package/lib/validation/ai-schemas.d.ts +56 -0
- package/lib/validation/ai-schemas.js +78 -0
- package/lib/validation/common-schemas.d.ts +133 -0
- package/lib/validation/common-schemas.js +94 -0
- package/lib/validation/index.d.ts +7 -0
- package/lib/validation/index.js +10 -0
- package/lib/validation/message-schemas.d.ts +160 -0
- package/lib/validation/message-schemas.js +156 -0
- package/lib/validation/middleware.d.ts +72 -0
- package/lib/validation/middleware.js +65 -0
- package/lib/validation/pipeline-schemas.d.ts +181 -0
- package/lib/validation/pipeline-schemas.js +101 -0
- package/lib/validation/pipeline-template-schemas.d.ts +155 -0
- package/lib/validation/pipeline-template-schemas.js +66 -0
- package/lib/validation/plugin-schemas.d.ts +171 -0
- package/lib/validation/plugin-schemas.js +81 -0
- package/openapi.yaml +292 -0
- package/package.json +167 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Redact AWS account identifiers from values headed for a DURABLE store or an
|
|
5
|
+
* outbound payload (audit `details`, ingested event `detail`, error messages).
|
|
6
|
+
*
|
|
7
|
+
* HARD CONSTRAINT: an AWS account id must NEVER be persisted anywhere in this
|
|
8
|
+
* system — `orgId` is the marketplace `customerIdentifier`, never an AWS account
|
|
9
|
+
* id. AWS surfaces (CodePipeline/CodeBuild failure messages, KMS/role ARNs) leak
|
|
10
|
+
* the account id as the 5th ARN segment or as a bare 12-digit token, so scrub at
|
|
11
|
+
* every boundary that writes untrusted AWS-derived text. Defense-in-depth: apply
|
|
12
|
+
* this even when an upstream (e.g. the events Lambda) is expected to have
|
|
13
|
+
* stripped it — the persistence boundary must not trust upstream.
|
|
14
|
+
*/
|
|
15
|
+
const REDACTED = '[REDACTED]';
|
|
16
|
+
/**
|
|
17
|
+
* A 12-digit run is the AWS account-id shape (and the account segment of every
|
|
18
|
+
* ARN). Bounded by non-digits so we never clip inside a longer number (e.g. a
|
|
19
|
+
* 13-digit millisecond timestamp is left intact).
|
|
20
|
+
*/
|
|
21
|
+
const AWS_ACCOUNT_ID_RE = /(?<!\d)\d{12}(?!\d)/g;
|
|
22
|
+
/** Keys that name an AWS account id outright — their value is dropped wholesale. */
|
|
23
|
+
const ACCOUNT_KEY_RE = /account/i;
|
|
24
|
+
/** Redact every AWS-account-id-shaped token (incl. the account field of any ARN) from a string. */
|
|
25
|
+
export function scrubAwsIdentifiersFromString(input) {
|
|
26
|
+
return input.replace(AWS_ACCOUNT_ID_RE, REDACTED);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Deep-scrub a value (string / array / plain object) of AWS account identifiers,
|
|
30
|
+
* returning a scrubbed copy — the input is never mutated. String values have
|
|
31
|
+
* account-id tokens redacted; any key matching {@link ACCOUNT_KEY_RE} with a
|
|
32
|
+
* string/number value is redacted wholesale. Non-string primitives pass through
|
|
33
|
+
* unchanged (a bare number is only redacted when it sits under an account-named
|
|
34
|
+
* key, to avoid clobbering legitimate numeric fields).
|
|
35
|
+
*/
|
|
36
|
+
export function scrubAwsIdentifiers(value) {
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
return scrubAwsIdentifiersFromString(value);
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return value.map((v) => scrubAwsIdentifiers(v));
|
|
42
|
+
}
|
|
43
|
+
if (value && typeof value === 'object') {
|
|
44
|
+
// Recurse plain AND null-prototype objects (data bags). A Date/Buffer/Map/
|
|
45
|
+
// class instance is NOT a plain object, and walking it via Object.entries
|
|
46
|
+
// would clobber it to `{}` (or a byte map) — silently destroying e.g. a Date
|
|
47
|
+
// held in audit `details`. But a null-prototype object (`Object.create(null)`,
|
|
48
|
+
// some JSON.parse revivers, BSON docs) is a plain data bag that must still be
|
|
49
|
+
// scrubbed — skipping it was a hole at the very persistence boundary this
|
|
50
|
+
// module guards. Leave only prototyped non-plain objects untouched.
|
|
51
|
+
const proto = Object.getPrototypeOf(value);
|
|
52
|
+
if (proto !== Object.prototype && proto !== null)
|
|
53
|
+
return value;
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [k, v] of Object.entries(value)) {
|
|
56
|
+
if (ACCOUNT_KEY_RE.test(k) && (typeof v === 'string' || typeof v === 'number')) {
|
|
57
|
+
out[k] = REDACTED;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
out[k] = scrubAwsIdentifiers(v);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXdzLXNjcnViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL2F3cy1zY3J1Yi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7OztHQVdHO0FBRUgsTUFBTSxRQUFRLEdBQUcsWUFBWSxDQUFDO0FBRTlCOzs7O0dBSUc7QUFDSCxNQUFNLGlCQUFpQixHQUFHLHNCQUFzQixDQUFDO0FBRWpELG9GQUFvRjtBQUNwRixNQUFNLGNBQWMsR0FBRyxVQUFVLENBQUM7QUFFbEMsbUdBQW1HO0FBQ25HLE1BQU0sVUFBVSw2QkFBNkIsQ0FBQyxLQUFhO0lBQ3pELE9BQU8sS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxRQUFRLENBQUMsQ0FBQztBQUNwRCxDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBSSxLQUFRO0lBQzdDLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDOUIsT0FBTyw2QkFBNkIsQ0FBQyxLQUFLLENBQWlCLENBQUM7SUFDOUQsQ0FBQztJQUNELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3pCLE9BQU8sS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLENBQWlCLENBQUM7SUFDbEUsQ0FBQztJQUNELElBQUksS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3ZDLDJFQUEyRTtRQUMzRSwwRUFBMEU7UUFDMUUsNkVBQTZFO1FBQzdFLCtFQUErRTtRQUMvRSw4RUFBOEU7UUFDOUUsMEVBQTBFO1FBQzFFLG9FQUFvRTtRQUNwRSxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzNDLElBQUksS0FBSyxLQUFLLE1BQU0sQ0FBQyxTQUFTLElBQUksS0FBSyxLQUFLLElBQUk7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUMvRCxNQUFNLEdBQUcsR0FBNEIsRUFBRSxDQUFDO1FBQ3hDLEtBQUssTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLEtBQWdDLENBQUMsRUFBRSxDQUFDO1lBQ3RFLElBQUksY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLFFBQVEsSUFBSSxPQUFPLENBQUMsS0FBSyxRQUFRLENBQUMsRUFBRSxDQUFDO2dCQUMvRSxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsUUFBUSxDQUFDO1lBQ3BCLENBQUM7aUJBQU0sQ0FBQztnQkFDTixHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEMsQ0FBQztRQUNILENBQUM7UUFDRCxPQUFPLEdBQVEsQ0FBQztJQUNsQixDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogUmVkYWN0IEFXUyBhY2NvdW50IGlkZW50aWZpZXJzIGZyb20gdmFsdWVzIGhlYWRlZCBmb3IgYSBEVVJBQkxFIHN0b3JlIG9yIGFuXG4gKiBvdXRib3VuZCBwYXlsb2FkIChhdWRpdCBgZGV0YWlsc2AsIGluZ2VzdGVkIGV2ZW50IGBkZXRhaWxgLCBlcnJvciBtZXNzYWdlcykuXG4gKlxuICogSEFSRCBDT05TVFJBSU5UOiBhbiBBV1MgYWNjb3VudCBpZCBtdXN0IE5FVkVSIGJlIHBlcnNpc3RlZCBhbnl3aGVyZSBpbiB0aGlzXG4gKiBzeXN0ZW0g4oCUIGBvcmdJZGAgaXMgdGhlIG1hcmtldHBsYWNlIGBjdXN0b21lcklkZW50aWZpZXJgLCBuZXZlciBhbiBBV1MgYWNjb3VudFxuICogaWQuIEFXUyBzdXJmYWNlcyAoQ29kZVBpcGVsaW5lL0NvZGVCdWlsZCBmYWlsdXJlIG1lc3NhZ2VzLCBLTVMvcm9sZSBBUk5zKSBsZWFrXG4gKiB0aGUgYWNjb3VudCBpZCBhcyB0aGUgNXRoIEFSTiBzZWdtZW50IG9yIGFzIGEgYmFyZSAxMi1kaWdpdCB0b2tlbiwgc28gc2NydWIgYXRcbiAqIGV2ZXJ5IGJvdW5kYXJ5IHRoYXQgd3JpdGVzIHVudHJ1c3RlZCBBV1MtZGVyaXZlZCB0ZXh0LiBEZWZlbnNlLWluLWRlcHRoOiBhcHBseVxuICogdGhpcyBldmVuIHdoZW4gYW4gdXBzdHJlYW0gKGUuZy4gdGhlIGV2ZW50cyBMYW1iZGEpIGlzIGV4cGVjdGVkIHRvIGhhdmVcbiAqIHN0cmlwcGVkIGl0IOKAlCB0aGUgcGVyc2lzdGVuY2UgYm91bmRhcnkgbXVzdCBub3QgdHJ1c3QgdXBzdHJlYW0uXG4gKi9cblxuY29uc3QgUkVEQUNURUQgPSAnW1JFREFDVEVEXSc7XG5cbi8qKlxuICogQSAxMi1kaWdpdCBydW4gaXMgdGhlIEFXUyBhY2NvdW50LWlkIHNoYXBlIChhbmQgdGhlIGFjY291bnQgc2VnbWVudCBvZiBldmVyeVxuICogQVJOKS4gQm91bmRlZCBieSBub24tZGlnaXRzIHNvIHdlIG5ldmVyIGNsaXAgaW5zaWRlIGEgbG9uZ2VyIG51bWJlciAoZS5nLiBhXG4gKiAxMy1kaWdpdCBtaWxsaXNlY29uZCB0aW1lc3RhbXAgaXMgbGVmdCBpbnRhY3QpLlxuICovXG5jb25zdCBBV1NfQUNDT1VOVF9JRF9SRSA9IC8oPzwhXFxkKVxcZHsxMn0oPyFcXGQpL2c7XG5cbi8qKiBLZXlzIHRoYXQgbmFtZSBhbiBBV1MgYWNjb3VudCBpZCBvdXRyaWdodCDigJQgdGhlaXIgdmFsdWUgaXMgZHJvcHBlZCB3aG9sZXNhbGUuICovXG5jb25zdCBBQ0NPVU5UX0tFWV9SRSA9IC9hY2NvdW50L2k7XG5cbi8qKiBSZWRhY3QgZXZlcnkgQVdTLWFjY291bnQtaWQtc2hhcGVkIHRva2VuIChpbmNsLiB0aGUgYWNjb3VudCBmaWVsZCBvZiBhbnkgQVJOKSBmcm9tIGEgc3RyaW5nLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNjcnViQXdzSWRlbnRpZmllcnNGcm9tU3RyaW5nKGlucHV0OiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gaW5wdXQucmVwbGFjZShBV1NfQUNDT1VOVF9JRF9SRSwgUkVEQUNURUQpO1xufVxuXG4vKipcbiAqIERlZXAtc2NydWIgYSB2YWx1ZSAoc3RyaW5nIC8gYXJyYXkgLyBwbGFpbiBvYmplY3QpIG9mIEFXUyBhY2NvdW50IGlkZW50aWZpZXJzLFxuICogcmV0dXJuaW5nIGEgc2NydWJiZWQgY29weSDigJQgdGhlIGlucHV0IGlzIG5ldmVyIG11dGF0ZWQuIFN0cmluZyB2YWx1ZXMgaGF2ZVxuICogYWNjb3VudC1pZCB0b2tlbnMgcmVkYWN0ZWQ7IGFueSBrZXkgbWF0Y2hpbmcge0BsaW5rIEFDQ09VTlRfS0VZX1JFfSB3aXRoIGFcbiAqIHN0cmluZy9udW1iZXIgdmFsdWUgaXMgcmVkYWN0ZWQgd2hvbGVzYWxlLiBOb24tc3RyaW5nIHByaW1pdGl2ZXMgcGFzcyB0aHJvdWdoXG4gKiB1bmNoYW5nZWQgKGEgYmFyZSBudW1iZXIgaXMgb25seSByZWRhY3RlZCB3aGVuIGl0IHNpdHMgdW5kZXIgYW4gYWNjb3VudC1uYW1lZFxuICoga2V5LCB0byBhdm9pZCBjbG9iYmVyaW5nIGxlZ2l0aW1hdGUgbnVtZXJpYyBmaWVsZHMpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2NydWJBd3NJZGVudGlmaWVyczxUPih2YWx1ZTogVCk6IFQge1xuICBpZiAodHlwZW9mIHZhbHVlID09PSAnc3RyaW5nJykge1xuICAgIHJldHVybiBzY3J1YkF3c0lkZW50aWZpZXJzRnJvbVN0cmluZyh2YWx1ZSkgYXMgdW5rbm93biBhcyBUO1xuICB9XG4gIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgIHJldHVybiB2YWx1ZS5tYXAoKHYpID0+IHNjcnViQXdzSWRlbnRpZmllcnModikpIGFzIHVua25vd24gYXMgVDtcbiAgfVxuICBpZiAodmFsdWUgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgIC8vIFJlY3Vyc2UgcGxhaW4gQU5EIG51bGwtcHJvdG90eXBlIG9iamVjdHMgKGRhdGEgYmFncykuIEEgRGF0ZS9CdWZmZXIvTWFwL1xuICAgIC8vIGNsYXNzIGluc3RhbmNlIGlzIE5PVCBhIHBsYWluIG9iamVjdCwgYW5kIHdhbGtpbmcgaXQgdmlhIE9iamVjdC5lbnRyaWVzXG4gICAgLy8gd291bGQgY2xvYmJlciBpdCB0byBge31gIChvciBhIGJ5dGUgbWFwKSDigJQgc2lsZW50bHkgZGVzdHJveWluZyBlLmcuIGEgRGF0ZVxuICAgIC8vIGhlbGQgaW4gYXVkaXQgYGRldGFpbHNgLiBCdXQgYSBudWxsLXByb3RvdHlwZSBvYmplY3QgKGBPYmplY3QuY3JlYXRlKG51bGwpYCxcbiAgICAvLyBzb21lIEpTT04ucGFyc2UgcmV2aXZlcnMsIEJTT04gZG9jcykgaXMgYSBwbGFpbiBkYXRhIGJhZyB0aGF0IG11c3Qgc3RpbGwgYmVcbiAgICAvLyBzY3J1YmJlZCDigJQgc2tpcHBpbmcgaXQgd2FzIGEgaG9sZSBhdCB0aGUgdmVyeSBwZXJzaXN0ZW5jZSBib3VuZGFyeSB0aGlzXG4gICAgLy8gbW9kdWxlIGd1YXJkcy4gTGVhdmUgb25seSBwcm90b3R5cGVkIG5vbi1wbGFpbiBvYmplY3RzIHVudG91Y2hlZC5cbiAgICBjb25zdCBwcm90byA9IE9iamVjdC5nZXRQcm90b3R5cGVPZih2YWx1ZSk7XG4gICAgaWYgKHByb3RvICE9PSBPYmplY3QucHJvdG90eXBlICYmIHByb3RvICE9PSBudWxsKSByZXR1cm4gdmFsdWU7XG4gICAgY29uc3Qgb3V0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICAgIGZvciAoY29uc3QgW2ssIHZdIG9mIE9iamVjdC5lbnRyaWVzKHZhbHVlIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KSkge1xuICAgICAgaWYgKEFDQ09VTlRfS0VZX1JFLnRlc3QoaykgJiYgKHR5cGVvZiB2ID09PSAnc3RyaW5nJyB8fCB0eXBlb2YgdiA9PT0gJ251bWJlcicpKSB7XG4gICAgICAgIG91dFtrXSA9IFJFREFDVEVEO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgb3V0W2tdID0gc2NydWJBd3NJZGVudGlmaWVycyh2KTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG91dCBhcyBUO1xuICB9XG4gIHJldHVybiB2YWx1ZTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project an entity into the compliance-event `attributes`, redacting secret
|
|
3
|
+
* VALUES while preserving everything the compliance engine actually evaluates.
|
|
4
|
+
* Recurses so nested secrets (deep inside a pipeline's `props`) are covered too.
|
|
5
|
+
*/
|
|
6
|
+
export declare function toComplianceAttributes(value: unknown): unknown;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Secret-redaction for compliance-event `attributes`.
|
|
5
|
+
*
|
|
6
|
+
* Shared by the plugin AND pipeline services (previously a byte-identical copy
|
|
7
|
+
* in each — a security-critical function that must not drift: a future "also
|
|
8
|
+
* redact `credentials`" fix has to protect BOTH services). Both feed entity
|
|
9
|
+
* attributes to the compliance engine over Redis, and neither may leak plaintext
|
|
10
|
+
* secrets there.
|
|
11
|
+
*
|
|
12
|
+
* The compliance rule engine reads secret maps (`env`/`buildArgs`, incl. nested
|
|
13
|
+
* `props.synth.env`, `props.stages[].steps[].env`, …) ONLY by KEY — `$keys(env)`,
|
|
14
|
+
* `$count(env)`, dot-path existence, key-`contains` — never by value (see
|
|
15
|
+
* api/compliance/src/engine/rule-operators.ts + its tests). So we preserve each
|
|
16
|
+
* secret map's shape + KEYS while overwriting every value with a marker; scalar
|
|
17
|
+
* secret fields (source `token`, passwords, …) are replaced outright. Structure
|
|
18
|
+
* and non-secret metadata pass through unchanged, so rule evaluation is
|
|
19
|
+
* identical while plaintext secrets never reach Redis or the compliance service.
|
|
20
|
+
*/
|
|
21
|
+
/** Marker written in place of a redacted secret VALUE. */
|
|
22
|
+
const REDACTED = '[REDACTED]';
|
|
23
|
+
/** Map keys whose (string→string) VALUES are secret build-time material. */
|
|
24
|
+
const SECRET_MAP_KEYS = new Set(['env', 'buildArgs']);
|
|
25
|
+
/**
|
|
26
|
+
* Scalar keys that themselves carry a secret string (tokens/passwords/etc.).
|
|
27
|
+
* Applied only to string values so boolean flags and declaration arrays (e.g.
|
|
28
|
+
* `secrets: PluginSecret[]`) are left intact.
|
|
29
|
+
*/
|
|
30
|
+
function isSecretScalarKey(key) {
|
|
31
|
+
const k = key.toLowerCase();
|
|
32
|
+
return k.includes('token') || k.includes('secret') || k.includes('password')
|
|
33
|
+
|| k.includes('passphrase') || k.includes('credential') || k.includes('apikey')
|
|
34
|
+
|| k.includes('accesskey') || k.includes('privatekey');
|
|
35
|
+
}
|
|
36
|
+
/** Only plain (Object-prototype) objects are traversed — Dates/class instances pass through intact. */
|
|
37
|
+
function isPlainObject(v) {
|
|
38
|
+
if (v === null || typeof v !== 'object')
|
|
39
|
+
return false;
|
|
40
|
+
const proto = Object.getPrototypeOf(v);
|
|
41
|
+
return proto === Object.prototype || proto === null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Project an entity into the compliance-event `attributes`, redacting secret
|
|
45
|
+
* VALUES while preserving everything the compliance engine actually evaluates.
|
|
46
|
+
* Recurses so nested secrets (deep inside a pipeline's `props`) are covered too.
|
|
47
|
+
*/
|
|
48
|
+
export function toComplianceAttributes(value) {
|
|
49
|
+
if (Array.isArray(value))
|
|
50
|
+
return value.map(toComplianceAttributes);
|
|
51
|
+
if (isPlainObject(value)) {
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [k, v] of Object.entries(value)) {
|
|
54
|
+
if (SECRET_MAP_KEYS.has(k) && isPlainObject(v)) {
|
|
55
|
+
// Preserve keys (compliance reads $keys/$count/presence), redact values.
|
|
56
|
+
out[k] = Object.fromEntries(Object.keys(v).map((mk) => [mk, REDACTED]));
|
|
57
|
+
}
|
|
58
|
+
else if (isSecretScalarKey(k) && typeof v === 'string') {
|
|
59
|
+
out[k] = REDACTED;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
out[k] = toComplianceAttributes(v);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
return value; // primitives, Date, null — untouched
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGxpYW5jZS1hdHRyaWJ1dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL2NvbXBsaWFuY2UtYXR0cmlidXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUVILDBEQUEwRDtBQUMxRCxNQUFNLFFBQVEsR0FBRyxZQUFZLENBQUM7QUFFOUIsNEVBQTRFO0FBQzVFLE1BQU0sZUFBZSxHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7QUFFdEQ7Ozs7R0FJRztBQUNILFNBQVMsaUJBQWlCLENBQUMsR0FBVztJQUNwQyxNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDNUIsT0FBTyxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUM7V0FDdkUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1dBQzVFLENBQUMsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUMzRCxDQUFDO0FBRUQsdUdBQXVHO0FBQ3ZHLFNBQVMsYUFBYSxDQUFDLENBQVU7SUFDL0IsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJLE9BQU8sQ0FBQyxLQUFLLFFBQVE7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUN0RCxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZDLE9BQU8sS0FBSyxLQUFLLE1BQU0sQ0FBQyxTQUFTLElBQUksS0FBSyxLQUFLLElBQUksQ0FBQztBQUN0RCxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxzQkFBc0IsQ0FBQyxLQUFjO0lBQ25ELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQyxHQUFHLENBQUMsc0JBQXNCLENBQUMsQ0FBQztJQUNuRSxJQUFJLGFBQWEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3pCLE1BQU0sR0FBRyxHQUE0QixFQUFFLENBQUM7UUFDeEMsS0FBSyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUMzQyxJQUFJLGVBQWUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7Z0JBQy9DLHlFQUF5RTtnQkFDekUsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMxRSxDQUFDO2lCQUFNLElBQUksaUJBQWlCLENBQUMsQ0FBQyxDQUFDLElBQUksT0FBTyxDQUFDLEtBQUssUUFBUSxFQUFFLENBQUM7Z0JBQ3pELEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxRQUFRLENBQUM7WUFDcEIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNyQyxDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDLENBQUMscUNBQXFDO0FBQ3JELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBTZWNyZXQtcmVkYWN0aW9uIGZvciBjb21wbGlhbmNlLWV2ZW50IGBhdHRyaWJ1dGVzYC5cbiAqXG4gKiBTaGFyZWQgYnkgdGhlIHBsdWdpbiBBTkQgcGlwZWxpbmUgc2VydmljZXMgKHByZXZpb3VzbHkgYSBieXRlLWlkZW50aWNhbCBjb3B5XG4gKiBpbiBlYWNoIOKAlCBhIHNlY3VyaXR5LWNyaXRpY2FsIGZ1bmN0aW9uIHRoYXQgbXVzdCBub3QgZHJpZnQ6IGEgZnV0dXJlIFwiYWxzb1xuICogcmVkYWN0IGBjcmVkZW50aWFsc2BcIiBmaXggaGFzIHRvIHByb3RlY3QgQk9USCBzZXJ2aWNlcykuIEJvdGggZmVlZCBlbnRpdHlcbiAqIGF0dHJpYnV0ZXMgdG8gdGhlIGNvbXBsaWFuY2UgZW5naW5lIG92ZXIgUmVkaXMsIGFuZCBuZWl0aGVyIG1heSBsZWFrIHBsYWludGV4dFxuICogc2VjcmV0cyB0aGVyZS5cbiAqXG4gKiBUaGUgY29tcGxpYW5jZSBydWxlIGVuZ2luZSByZWFkcyBzZWNyZXQgbWFwcyAoYGVudmAvYGJ1aWxkQXJnc2AsIGluY2wuIG5lc3RlZFxuICogYHByb3BzLnN5bnRoLmVudmAsIGBwcm9wcy5zdGFnZXNbXS5zdGVwc1tdLmVudmAsIOKApikgT05MWSBieSBLRVkg4oCUIGAka2V5cyhlbnYpYCxcbiAqIGAkY291bnQoZW52KWAsIGRvdC1wYXRoIGV4aXN0ZW5jZSwga2V5LWBjb250YWluc2Ag4oCUIG5ldmVyIGJ5IHZhbHVlIChzZWVcbiAqIGFwaS9jb21wbGlhbmNlL3NyYy9lbmdpbmUvcnVsZS1vcGVyYXRvcnMudHMgKyBpdHMgdGVzdHMpLiBTbyB3ZSBwcmVzZXJ2ZSBlYWNoXG4gKiBzZWNyZXQgbWFwJ3Mgc2hhcGUgKyBLRVlTIHdoaWxlIG92ZXJ3cml0aW5nIGV2ZXJ5IHZhbHVlIHdpdGggYSBtYXJrZXI7IHNjYWxhclxuICogc2VjcmV0IGZpZWxkcyAoc291cmNlIGB0b2tlbmAsIHBhc3N3b3Jkcywg4oCmKSBhcmUgcmVwbGFjZWQgb3V0cmlnaHQuIFN0cnVjdHVyZVxuICogYW5kIG5vbi1zZWNyZXQgbWV0YWRhdGEgcGFzcyB0aHJvdWdoIHVuY2hhbmdlZCwgc28gcnVsZSBldmFsdWF0aW9uIGlzXG4gKiBpZGVudGljYWwgd2hpbGUgcGxhaW50ZXh0IHNlY3JldHMgbmV2ZXIgcmVhY2ggUmVkaXMgb3IgdGhlIGNvbXBsaWFuY2Ugc2VydmljZS5cbiAqL1xuXG4vKiogTWFya2VyIHdyaXR0ZW4gaW4gcGxhY2Ugb2YgYSByZWRhY3RlZCBzZWNyZXQgVkFMVUUuICovXG5jb25zdCBSRURBQ1RFRCA9ICdbUkVEQUNURURdJztcblxuLyoqIE1hcCBrZXlzIHdob3NlIChzdHJpbmfihpJzdHJpbmcpIFZBTFVFUyBhcmUgc2VjcmV0IGJ1aWxkLXRpbWUgbWF0ZXJpYWwuICovXG5jb25zdCBTRUNSRVRfTUFQX0tFWVMgPSBuZXcgU2V0KFsnZW52JywgJ2J1aWxkQXJncyddKTtcblxuLyoqXG4gKiBTY2FsYXIga2V5cyB0aGF0IHRoZW1zZWx2ZXMgY2FycnkgYSBzZWNyZXQgc3RyaW5nICh0b2tlbnMvcGFzc3dvcmRzL2V0Yy4pLlxuICogQXBwbGllZCBvbmx5IHRvIHN0cmluZyB2YWx1ZXMgc28gYm9vbGVhbiBmbGFncyBhbmQgZGVjbGFyYXRpb24gYXJyYXlzIChlLmcuXG4gKiBgc2VjcmV0czogUGx1Z2luU2VjcmV0W11gKSBhcmUgbGVmdCBpbnRhY3QuXG4gKi9cbmZ1bmN0aW9uIGlzU2VjcmV0U2NhbGFyS2V5KGtleTogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGNvbnN0IGsgPSBrZXkudG9Mb3dlckNhc2UoKTtcbiAgcmV0dXJuIGsuaW5jbHVkZXMoJ3Rva2VuJykgfHwgay5pbmNsdWRlcygnc2VjcmV0JykgfHwgay5pbmNsdWRlcygncGFzc3dvcmQnKVxuICAgIHx8IGsuaW5jbHVkZXMoJ3Bhc3NwaHJhc2UnKSB8fCBrLmluY2x1ZGVzKCdjcmVkZW50aWFsJykgfHwgay5pbmNsdWRlcygnYXBpa2V5JylcbiAgICB8fCBrLmluY2x1ZGVzKCdhY2Nlc3NrZXknKSB8fCBrLmluY2x1ZGVzKCdwcml2YXRla2V5Jyk7XG59XG5cbi8qKiBPbmx5IHBsYWluIChPYmplY3QtcHJvdG90eXBlKSBvYmplY3RzIGFyZSB0cmF2ZXJzZWQg4oCUIERhdGVzL2NsYXNzIGluc3RhbmNlcyBwYXNzIHRocm91Z2ggaW50YWN0LiAqL1xuZnVuY3Rpb24gaXNQbGFpbk9iamVjdCh2OiB1bmtub3duKTogdiBpcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIGlmICh2ID09PSBudWxsIHx8IHR5cGVvZiB2ICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuICBjb25zdCBwcm90byA9IE9iamVjdC5nZXRQcm90b3R5cGVPZih2KTtcbiAgcmV0dXJuIHByb3RvID09PSBPYmplY3QucHJvdG90eXBlIHx8IHByb3RvID09PSBudWxsO1xufVxuXG4vKipcbiAqIFByb2plY3QgYW4gZW50aXR5IGludG8gdGhlIGNvbXBsaWFuY2UtZXZlbnQgYGF0dHJpYnV0ZXNgLCByZWRhY3Rpbmcgc2VjcmV0XG4gKiBWQUxVRVMgd2hpbGUgcHJlc2VydmluZyBldmVyeXRoaW5nIHRoZSBjb21wbGlhbmNlIGVuZ2luZSBhY3R1YWxseSBldmFsdWF0ZXMuXG4gKiBSZWN1cnNlcyBzbyBuZXN0ZWQgc2VjcmV0cyAoZGVlcCBpbnNpZGUgYSBwaXBlbGluZSdzIGBwcm9wc2ApIGFyZSBjb3ZlcmVkIHRvby5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvQ29tcGxpYW5jZUF0dHJpYnV0ZXModmFsdWU6IHVua25vd24pOiB1bmtub3duIHtcbiAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSByZXR1cm4gdmFsdWUubWFwKHRvQ29tcGxpYW5jZUF0dHJpYnV0ZXMpO1xuICBpZiAoaXNQbGFpbk9iamVjdCh2YWx1ZSkpIHtcbiAgICBjb25zdCBvdXQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG4gICAgZm9yIChjb25zdCBbaywgdl0gb2YgT2JqZWN0LmVudHJpZXModmFsdWUpKSB7XG4gICAgICBpZiAoU0VDUkVUX01BUF9LRVlTLmhhcyhrKSAmJiBpc1BsYWluT2JqZWN0KHYpKSB7XG4gICAgICAgIC8vIFByZXNlcnZlIGtleXMgKGNvbXBsaWFuY2UgcmVhZHMgJGtleXMvJGNvdW50L3ByZXNlbmNlKSwgcmVkYWN0IHZhbHVlcy5cbiAgICAgICAgb3V0W2tdID0gT2JqZWN0LmZyb21FbnRyaWVzKE9iamVjdC5rZXlzKHYpLm1hcCgobWspID0+IFttaywgUkVEQUNURURdKSk7XG4gICAgICB9IGVsc2UgaWYgKGlzU2VjcmV0U2NhbGFyS2V5KGspICYmIHR5cGVvZiB2ID09PSAnc3RyaW5nJykge1xuICAgICAgICBvdXRba10gPSBSRURBQ1RFRDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIG91dFtrXSA9IHRvQ29tcGxpYW5jZUF0dHJpYnV0ZXModik7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvdXQ7XG4gIH1cbiAgcmV0dXJuIHZhbHVlOyAvLyBwcmltaXRpdmVzLCBEYXRlLCBudWxsIOKAlCB1bnRvdWNoZWRcbn1cbiJdfQ==
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `worker` over `items` with at most `limit` invocations in flight.
|
|
3
|
+
* Preserves order in the returned array. Rejects on the first error.
|
|
4
|
+
*
|
|
5
|
+
* Use this anywhere we fan out N HTTP calls and want to cap concurrency
|
|
6
|
+
* (e.g. registry blob mounts, parallel manifest fetches). Hand-rolled to
|
|
7
|
+
* avoid adding a dep — keeps the bundle/footprint tiny.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runConcurrent<T, R>(items: T[], limit: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Run `worker` over `items` with at most `limit` invocations in flight.
|
|
5
|
+
* Preserves order in the returned array. Rejects on the first error.
|
|
6
|
+
*
|
|
7
|
+
* Use this anywhere we fan out N HTTP calls and want to cap concurrency
|
|
8
|
+
* (e.g. registry blob mounts, parallel manifest fetches). Hand-rolled to
|
|
9
|
+
* avoid adding a dep — keeps the bundle/footprint tiny.
|
|
10
|
+
*/
|
|
11
|
+
export async function runConcurrent(items, limit, worker) {
|
|
12
|
+
if (limit < 1)
|
|
13
|
+
throw new Error('runConcurrent: limit must be >= 1');
|
|
14
|
+
if (items.length === 0)
|
|
15
|
+
return [];
|
|
16
|
+
const results = new Array(items.length);
|
|
17
|
+
let nextIndex = 0;
|
|
18
|
+
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
19
|
+
while (true) {
|
|
20
|
+
const i = nextIndex++;
|
|
21
|
+
if (i >= items.length)
|
|
22
|
+
return;
|
|
23
|
+
results[i] = await worker(items[i], i);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
await Promise.all(runners);
|
|
27
|
+
return results;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uY3VycmVuY3kuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvY29uY3VycmVuY3kudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxhQUFhLENBQ2pDLEtBQVUsRUFDVixLQUFhLEVBQ2IsTUFBOEM7SUFFOUMsSUFBSSxLQUFLLEdBQUcsQ0FBQztRQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsbUNBQW1DLENBQUMsQ0FBQztJQUNwRSxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sRUFBRSxDQUFDO0lBRWxDLE1BQU0sT0FBTyxHQUFHLElBQUksS0FBSyxDQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUMzQyxJQUFJLFNBQVMsR0FBRyxDQUFDLENBQUM7SUFFbEIsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRSxLQUFLLElBQUksRUFBRTtRQUMvRSxPQUFPLElBQUksRUFBRSxDQUFDO1lBQ1osTUFBTSxDQUFDLEdBQUcsU0FBUyxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU07Z0JBQUUsT0FBTztZQUM5QixPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsTUFBTSxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ3pDLENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQztJQUVILE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUMzQixPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogUnVuIGB3b3JrZXJgIG92ZXIgYGl0ZW1zYCB3aXRoIGF0IG1vc3QgYGxpbWl0YCBpbnZvY2F0aW9ucyBpbiBmbGlnaHQuXG4gKiBQcmVzZXJ2ZXMgb3JkZXIgaW4gdGhlIHJldHVybmVkIGFycmF5LiBSZWplY3RzIG9uIHRoZSBmaXJzdCBlcnJvci5cbiAqXG4gKiBVc2UgdGhpcyBhbnl3aGVyZSB3ZSBmYW4gb3V0IE4gSFRUUCBjYWxscyBhbmQgd2FudCB0byBjYXAgY29uY3VycmVuY3lcbiAqIChlLmcuIHJlZ2lzdHJ5IGJsb2IgbW91bnRzLCBwYXJhbGxlbCBtYW5pZmVzdCBmZXRjaGVzKS4gSGFuZC1yb2xsZWQgdG9cbiAqIGF2b2lkIGFkZGluZyBhIGRlcCDigJQga2VlcHMgdGhlIGJ1bmRsZS9mb290cHJpbnQgdGlueS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bkNvbmN1cnJlbnQ8VCwgUj4oXG4gIGl0ZW1zOiBUW10sXG4gIGxpbWl0OiBudW1iZXIsXG4gIHdvcmtlcjogKGl0ZW06IFQsIGluZGV4OiBudW1iZXIpID0+IFByb21pc2U8Uj4sXG4pOiBQcm9taXNlPFJbXT4ge1xuICBpZiAobGltaXQgPCAxKSB0aHJvdyBuZXcgRXJyb3IoJ3J1bkNvbmN1cnJlbnQ6IGxpbWl0IG11c3QgYmUgPj0gMScpO1xuICBpZiAoaXRlbXMubGVuZ3RoID09PSAwKSByZXR1cm4gW107XG5cbiAgY29uc3QgcmVzdWx0cyA9IG5ldyBBcnJheTxSPihpdGVtcy5sZW5ndGgpO1xuICBsZXQgbmV4dEluZGV4ID0gMDtcblxuICBjb25zdCBydW5uZXJzID0gQXJyYXkuZnJvbSh7IGxlbmd0aDogTWF0aC5taW4obGltaXQsIGl0ZW1zLmxlbmd0aCkgfSwgYXN5bmMgKCkgPT4ge1xuICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICBjb25zdCBpID0gbmV4dEluZGV4Kys7XG4gICAgICBpZiAoaSA+PSBpdGVtcy5sZW5ndGgpIHJldHVybjtcbiAgICAgIHJlc3VsdHNbaV0gPSBhd2FpdCB3b3JrZXIoaXRlbXNbaV0sIGkpO1xuICAgIH1cbiAgfSk7XG5cbiAgYXdhaXQgUHJvbWlzZS5hbGwocnVubmVycyk7XG4gIHJldHVybiByZXN1bHRzO1xufVxuIl19
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Logger } from 'winston';
|
|
2
|
+
/**
|
|
3
|
+
* Install last-resort process-level fault handlers.
|
|
4
|
+
*
|
|
5
|
+
* Without these, an uncaught exception or an unhandled promise rejection in a
|
|
6
|
+
* fire-and-forget async path crashes the container with NO structured log line
|
|
7
|
+
* (and on modern Node terminates the process by default). That makes incidents
|
|
8
|
+
* undebuggable from logs. This logs the fault through the service logger, then
|
|
9
|
+
* exits non-zero so the orchestrator restarts a CLEAN process — a faulted
|
|
10
|
+
* process is in an undefined state and must not keep serving.
|
|
11
|
+
*
|
|
12
|
+
* This is orthogonal to graceful SIGTERM shutdown (handled in api-server /
|
|
13
|
+
* platform): that drains on a normal stop; this is the abnormal-fault path.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent — call once at process start; repeat calls are no-ops. Skipped
|
|
16
|
+
* under NODE_ENV=test so it never competes with the test runner's own handlers.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installCrashHandlers(log?: Logger): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { createLogger } from './logger.js';
|
|
4
|
+
let installed = false;
|
|
5
|
+
/**
|
|
6
|
+
* Install last-resort process-level fault handlers.
|
|
7
|
+
*
|
|
8
|
+
* Without these, an uncaught exception or an unhandled promise rejection in a
|
|
9
|
+
* fire-and-forget async path crashes the container with NO structured log line
|
|
10
|
+
* (and on modern Node terminates the process by default). That makes incidents
|
|
11
|
+
* undebuggable from logs. This logs the fault through the service logger, then
|
|
12
|
+
* exits non-zero so the orchestrator restarts a CLEAN process — a faulted
|
|
13
|
+
* process is in an undefined state and must not keep serving.
|
|
14
|
+
*
|
|
15
|
+
* This is orthogonal to graceful SIGTERM shutdown (handled in api-server /
|
|
16
|
+
* platform): that drains on a normal stop; this is the abnormal-fault path.
|
|
17
|
+
*
|
|
18
|
+
* Idempotent — call once at process start; repeat calls are no-ops. Skipped
|
|
19
|
+
* under NODE_ENV=test so it never competes with the test runner's own handlers.
|
|
20
|
+
*/
|
|
21
|
+
export function installCrashHandlers(log = createLogger('process')) {
|
|
22
|
+
if (installed || process.env.NODE_ENV === 'test')
|
|
23
|
+
return;
|
|
24
|
+
installed = true;
|
|
25
|
+
const fatal = (kind, error, extra = {}) => {
|
|
26
|
+
log.error(`${kind} — exiting for restart`, { error: error.message, stack: error.stack, ...extra });
|
|
27
|
+
// Give winston a tick to flush async transports, then exit. `unref` so this
|
|
28
|
+
// timer never keeps an otherwise-idle process alive.
|
|
29
|
+
setTimeout(() => process.exit(1), 100).unref();
|
|
30
|
+
};
|
|
31
|
+
process.on('uncaughtException', (error, origin) => fatal('Uncaught exception', error, { origin }));
|
|
32
|
+
process.on('unhandledRejection', (reason) => {
|
|
33
|
+
fatal('Unhandled promise rejection', reason instanceof Error ? reason : new Error(String(reason)));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3Jhc2gtaGFuZGxlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvY3Jhc2gtaGFuZGxlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUd0QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRTNDLElBQUksU0FBUyxHQUFHLEtBQUssQ0FBQztBQUV0Qjs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLFVBQVUsb0JBQW9CLENBQUMsR0FBRyxHQUFXLFlBQVksQ0FBQyxTQUFTLENBQUM7SUFDeEUsSUFBSSxTQUFTLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEtBQUssTUFBTTtRQUFFLE9BQU87SUFDekQsU0FBUyxHQUFHLElBQUksQ0FBQztJQUVqQixNQUFNLEtBQUssR0FBRyxDQUFDLElBQVksRUFBRSxLQUFZLEVBQUUsS0FBSyxHQUE0QixFQUFFLEVBQVEsRUFBRTtRQUN0RixHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSx3QkFBd0IsRUFBRSxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsS0FBSyxFQUFFLEdBQUcsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNuRyw0RUFBNEU7UUFDNUUscURBQXFEO1FBQ3JELFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ2pELENBQUMsQ0FBQztJQUVGLE9BQU8sQ0FBQyxFQUFFLENBQUMsbUJBQW1CLEVBQUUsQ0FBQyxLQUFZLEVBQUUsTUFBYyxFQUFFLEVBQUUsQ0FBQyxLQUFLLENBQUMsb0JBQW9CLEVBQUUsS0FBSyxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2xILE9BQU8sQ0FBQyxFQUFFLENBQUMsb0JBQW9CLEVBQUUsQ0FBQyxNQUFlLEVBQUUsRUFBRTtRQUNuRCxLQUFLLENBQUMsNkJBQTZCLEVBQUUsTUFBTSxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3JHLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IExvZ2dlciB9IGZyb20gJ3dpbnN0b24nO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi9sb2dnZXIuanMnO1xuXG5sZXQgaW5zdGFsbGVkID0gZmFsc2U7XG5cbi8qKlxuICogSW5zdGFsbCBsYXN0LXJlc29ydCBwcm9jZXNzLWxldmVsIGZhdWx0IGhhbmRsZXJzLlxuICpcbiAqIFdpdGhvdXQgdGhlc2UsIGFuIHVuY2F1Z2h0IGV4Y2VwdGlvbiBvciBhbiB1bmhhbmRsZWQgcHJvbWlzZSByZWplY3Rpb24gaW4gYVxuICogZmlyZS1hbmQtZm9yZ2V0IGFzeW5jIHBhdGggY3Jhc2hlcyB0aGUgY29udGFpbmVyIHdpdGggTk8gc3RydWN0dXJlZCBsb2cgbGluZVxuICogKGFuZCBvbiBtb2Rlcm4gTm9kZSB0ZXJtaW5hdGVzIHRoZSBwcm9jZXNzIGJ5IGRlZmF1bHQpLiBUaGF0IG1ha2VzIGluY2lkZW50c1xuICogdW5kZWJ1Z2dhYmxlIGZyb20gbG9ncy4gVGhpcyBsb2dzIHRoZSBmYXVsdCB0aHJvdWdoIHRoZSBzZXJ2aWNlIGxvZ2dlciwgdGhlblxuICogZXhpdHMgbm9uLXplcm8gc28gdGhlIG9yY2hlc3RyYXRvciByZXN0YXJ0cyBhIENMRUFOIHByb2Nlc3Mg4oCUIGEgZmF1bHRlZFxuICogcHJvY2VzcyBpcyBpbiBhbiB1bmRlZmluZWQgc3RhdGUgYW5kIG11c3Qgbm90IGtlZXAgc2VydmluZy5cbiAqXG4gKiBUaGlzIGlzIG9ydGhvZ29uYWwgdG8gZ3JhY2VmdWwgU0lHVEVSTSBzaHV0ZG93biAoaGFuZGxlZCBpbiBhcGktc2VydmVyIC9cbiAqIHBsYXRmb3JtKTogdGhhdCBkcmFpbnMgb24gYSBub3JtYWwgc3RvcDsgdGhpcyBpcyB0aGUgYWJub3JtYWwtZmF1bHQgcGF0aC5cbiAqXG4gKiBJZGVtcG90ZW50IOKAlCBjYWxsIG9uY2UgYXQgcHJvY2VzcyBzdGFydDsgcmVwZWF0IGNhbGxzIGFyZSBuby1vcHMuIFNraXBwZWRcbiAqIHVuZGVyIE5PREVfRU5WPXRlc3Qgc28gaXQgbmV2ZXIgY29tcGV0ZXMgd2l0aCB0aGUgdGVzdCBydW5uZXIncyBvd24gaGFuZGxlcnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbnN0YWxsQ3Jhc2hIYW5kbGVycyhsb2c6IExvZ2dlciA9IGNyZWF0ZUxvZ2dlcigncHJvY2VzcycpKTogdm9pZCB7XG4gIGlmIChpbnN0YWxsZWQgfHwgcHJvY2Vzcy5lbnYuTk9ERV9FTlYgPT09ICd0ZXN0JykgcmV0dXJuO1xuICBpbnN0YWxsZWQgPSB0cnVlO1xuXG4gIGNvbnN0IGZhdGFsID0gKGtpbmQ6IHN0cmluZywgZXJyb3I6IEVycm9yLCBleHRyYTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fSk6IHZvaWQgPT4ge1xuICAgIGxvZy5lcnJvcihgJHtraW5kfSDigJQgZXhpdGluZyBmb3IgcmVzdGFydGAsIHsgZXJyb3I6IGVycm9yLm1lc3NhZ2UsIHN0YWNrOiBlcnJvci5zdGFjaywgLi4uZXh0cmEgfSk7XG4gICAgLy8gR2l2ZSB3aW5zdG9uIGEgdGljayB0byBmbHVzaCBhc3luYyB0cmFuc3BvcnRzLCB0aGVuIGV4aXQuIGB1bnJlZmAgc28gdGhpc1xuICAgIC8vIHRpbWVyIG5ldmVyIGtlZXBzIGFuIG90aGVyd2lzZS1pZGxlIHByb2Nlc3MgYWxpdmUuXG4gICAgc2V0VGltZW91dCgoKSA9PiBwcm9jZXNzLmV4aXQoMSksIDEwMCkudW5yZWYoKTtcbiAgfTtcblxuICBwcm9jZXNzLm9uKCd1bmNhdWdodEV4Y2VwdGlvbicsIChlcnJvcjogRXJyb3IsIG9yaWdpbjogc3RyaW5nKSA9PiBmYXRhbCgnVW5jYXVnaHQgZXhjZXB0aW9uJywgZXJyb3IsIHsgb3JpZ2luIH0pKTtcbiAgcHJvY2Vzcy5vbigndW5oYW5kbGVkUmVqZWN0aW9uJywgKHJlYXNvbjogdW5rbm93bikgPT4ge1xuICAgIGZhdGFsKCdVbmhhbmRsZWQgcHJvbWlzZSByZWplY3Rpb24nLCByZWFzb24gaW5zdGFuY2VvZiBFcnJvciA/IHJlYXNvbiA6IG5ldyBFcnJvcihTdHJpbmcocmVhc29uKSkpO1xuICB9KTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed environment-variable readers with consistent parsing + defaults.
|
|
3
|
+
*
|
|
4
|
+
* Services previously hand-rolled `parseInt(process.env.X || '…', 10)` /
|
|
5
|
+
* `process.env.X === 'true'` at hundreds of call sites — which drifts (the same
|
|
6
|
+
* var parsed with different defaults in different packages) and silently returns
|
|
7
|
+
* the default on a typo'd name. These helpers centralize the parsing so a value
|
|
8
|
+
* is read the same way everywhere, and make shared constants importable rather
|
|
9
|
+
* than re-derived (see `MAX_PAGE_LIMIT` / `DEFAULT_PAGE_LIMIT` in
|
|
10
|
+
* `validation/common-schemas.ts`).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Parse an integer env var. Unset / blank / non-numeric → `def`. Optional
|
|
14
|
+
* `min`/`max` clamp the result (applied after the default).
|
|
15
|
+
*/
|
|
16
|
+
export declare function envInt(name: string, def: number, opts?: {
|
|
17
|
+
min?: number;
|
|
18
|
+
max?: number;
|
|
19
|
+
}): number;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a boolean env var. `true`/`1`/`yes` → true, `false`/`0`/`no` → false
|
|
22
|
+
* (case-insensitive); unset / blank / unrecognized → `def`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function envBool(name: string, def: boolean): boolean;
|
|
25
|
+
/** Read an env var constrained to `allowed`; unset / not-in-set → `def`. */
|
|
26
|
+
export declare function envEnum<T extends string>(name: string, allowed: readonly T[], def: T): T;
|
|
27
|
+
/** Read a string env var; unset / blank → `def`. */
|
|
28
|
+
export declare function envStr(name: string, def: string): string;
|
package/lib/utils/env.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Typed environment-variable readers with consistent parsing + defaults.
|
|
5
|
+
*
|
|
6
|
+
* Services previously hand-rolled `parseInt(process.env.X || '…', 10)` /
|
|
7
|
+
* `process.env.X === 'true'` at hundreds of call sites — which drifts (the same
|
|
8
|
+
* var parsed with different defaults in different packages) and silently returns
|
|
9
|
+
* the default on a typo'd name. These helpers centralize the parsing so a value
|
|
10
|
+
* is read the same way everywhere, and make shared constants importable rather
|
|
11
|
+
* than re-derived (see `MAX_PAGE_LIMIT` / `DEFAULT_PAGE_LIMIT` in
|
|
12
|
+
* `validation/common-schemas.ts`).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Parse an integer env var. Unset / blank / non-numeric → `def`. Optional
|
|
16
|
+
* `min`/`max` clamp the result (applied after the default).
|
|
17
|
+
*/
|
|
18
|
+
export function envInt(name, def, opts) {
|
|
19
|
+
const raw = process.env[name];
|
|
20
|
+
let n = raw === undefined || raw.trim() === '' ? def : Number.parseInt(raw, 10);
|
|
21
|
+
if (!Number.isFinite(n))
|
|
22
|
+
n = def;
|
|
23
|
+
if (opts?.min !== undefined)
|
|
24
|
+
n = Math.max(opts.min, n);
|
|
25
|
+
if (opts?.max !== undefined)
|
|
26
|
+
n = Math.min(opts.max, n);
|
|
27
|
+
return n;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a boolean env var. `true`/`1`/`yes` → true, `false`/`0`/`no` → false
|
|
31
|
+
* (case-insensitive); unset / blank / unrecognized → `def`.
|
|
32
|
+
*/
|
|
33
|
+
export function envBool(name, def) {
|
|
34
|
+
const raw = process.env[name];
|
|
35
|
+
if (raw === undefined || raw.trim() === '')
|
|
36
|
+
return def;
|
|
37
|
+
const v = raw.trim().toLowerCase();
|
|
38
|
+
if (v === 'true' || v === '1' || v === 'yes')
|
|
39
|
+
return true;
|
|
40
|
+
if (v === 'false' || v === '0' || v === 'no')
|
|
41
|
+
return false;
|
|
42
|
+
return def;
|
|
43
|
+
}
|
|
44
|
+
/** Read an env var constrained to `allowed`; unset / not-in-set → `def`. */
|
|
45
|
+
export function envEnum(name, allowed, def) {
|
|
46
|
+
const raw = process.env[name]?.trim();
|
|
47
|
+
return raw !== undefined && allowed.includes(raw) ? raw : def;
|
|
48
|
+
}
|
|
49
|
+
/** Read a string env var; unset / blank → `def`. */
|
|
50
|
+
export function envStr(name, def) {
|
|
51
|
+
const raw = process.env[name];
|
|
52
|
+
return raw === undefined || raw.trim() === '' ? def : raw;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL2Vudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7O0dBVUc7QUFFSDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsTUFBTSxDQUFDLElBQVksRUFBRSxHQUFXLEVBQUUsSUFBcUM7SUFDckYsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM5QixJQUFJLENBQUMsR0FBRyxHQUFHLEtBQUssU0FBUyxJQUFJLEdBQUcsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDaEYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO1FBQUUsQ0FBQyxHQUFHLEdBQUcsQ0FBQztJQUNqQyxJQUFJLElBQUksRUFBRSxHQUFHLEtBQUssU0FBUztRQUFFLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDdkQsSUFBSSxJQUFJLEVBQUUsR0FBRyxLQUFLLFNBQVM7UUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3ZELE9BQU8sQ0FBQyxDQUFDO0FBQ1gsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxPQUFPLENBQUMsSUFBWSxFQUFFLEdBQVk7SUFDaEQsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM5QixJQUFJLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUU7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUN2RCxNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDbkMsSUFBSSxDQUFDLEtBQUssTUFBTSxJQUFJLENBQUMsS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLEtBQUs7UUFBRSxPQUFPLElBQUksQ0FBQztJQUMxRCxJQUFJLENBQUMsS0FBSyxPQUFPLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssSUFBSTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQzNELE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVELDRFQUE0RTtBQUM1RSxNQUFNLFVBQVUsT0FBTyxDQUFtQixJQUFZLEVBQUUsT0FBcUIsRUFBRSxHQUFNO0lBQ25GLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFtQixDQUFDO0lBQ3ZELE9BQU8sR0FBRyxLQUFLLFNBQVMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztBQUNoRSxDQUFDO0FBRUQsb0RBQW9EO0FBQ3BELE1BQU0sVUFBVSxNQUFNLENBQUMsSUFBWSxFQUFFLEdBQVc7SUFDOUMsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM5QixPQUFPLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7QUFDNUQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFR5cGVkIGVudmlyb25tZW50LXZhcmlhYmxlIHJlYWRlcnMgd2l0aCBjb25zaXN0ZW50IHBhcnNpbmcgKyBkZWZhdWx0cy5cbiAqXG4gKiBTZXJ2aWNlcyBwcmV2aW91c2x5IGhhbmQtcm9sbGVkIGBwYXJzZUludChwcm9jZXNzLmVudi5YIHx8ICfigKYnLCAxMClgIC9cbiAqIGBwcm9jZXNzLmVudi5YID09PSAndHJ1ZSdgIGF0IGh1bmRyZWRzIG9mIGNhbGwgc2l0ZXMg4oCUIHdoaWNoIGRyaWZ0cyAodGhlIHNhbWVcbiAqIHZhciBwYXJzZWQgd2l0aCBkaWZmZXJlbnQgZGVmYXVsdHMgaW4gZGlmZmVyZW50IHBhY2thZ2VzKSBhbmQgc2lsZW50bHkgcmV0dXJuc1xuICogdGhlIGRlZmF1bHQgb24gYSB0eXBvJ2QgbmFtZS4gVGhlc2UgaGVscGVycyBjZW50cmFsaXplIHRoZSBwYXJzaW5nIHNvIGEgdmFsdWVcbiAqIGlzIHJlYWQgdGhlIHNhbWUgd2F5IGV2ZXJ5d2hlcmUsIGFuZCBtYWtlIHNoYXJlZCBjb25zdGFudHMgaW1wb3J0YWJsZSByYXRoZXJcbiAqIHRoYW4gcmUtZGVyaXZlZCAoc2VlIGBNQVhfUEFHRV9MSU1JVGAgLyBgREVGQVVMVF9QQUdFX0xJTUlUYCBpblxuICogYHZhbGlkYXRpb24vY29tbW9uLXNjaGVtYXMudHNgKS5cbiAqL1xuXG4vKipcbiAqIFBhcnNlIGFuIGludGVnZXIgZW52IHZhci4gVW5zZXQgLyBibGFuayAvIG5vbi1udW1lcmljIOKGkiBgZGVmYC4gT3B0aW9uYWxcbiAqIGBtaW5gL2BtYXhgIGNsYW1wIHRoZSByZXN1bHQgKGFwcGxpZWQgYWZ0ZXIgdGhlIGRlZmF1bHQpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZW52SW50KG5hbWU6IHN0cmluZywgZGVmOiBudW1iZXIsIG9wdHM/OiB7IG1pbj86IG51bWJlcjsgbWF4PzogbnVtYmVyIH0pOiBudW1iZXIge1xuICBjb25zdCByYXcgPSBwcm9jZXNzLmVudltuYW1lXTtcbiAgbGV0IG4gPSByYXcgPT09IHVuZGVmaW5lZCB8fCByYXcudHJpbSgpID09PSAnJyA/IGRlZiA6IE51bWJlci5wYXJzZUludChyYXcsIDEwKTtcbiAgaWYgKCFOdW1iZXIuaXNGaW5pdGUobikpIG4gPSBkZWY7XG4gIGlmIChvcHRzPy5taW4gIT09IHVuZGVmaW5lZCkgbiA9IE1hdGgubWF4KG9wdHMubWluLCBuKTtcbiAgaWYgKG9wdHM/Lm1heCAhPT0gdW5kZWZpbmVkKSBuID0gTWF0aC5taW4ob3B0cy5tYXgsIG4pO1xuICByZXR1cm4gbjtcbn1cblxuLyoqXG4gKiBQYXJzZSBhIGJvb2xlYW4gZW52IHZhci4gYHRydWVgL2AxYC9geWVzYCDihpIgdHJ1ZSwgYGZhbHNlYC9gMGAvYG5vYCDihpIgZmFsc2VcbiAqIChjYXNlLWluc2Vuc2l0aXZlKTsgdW5zZXQgLyBibGFuayAvIHVucmVjb2duaXplZCDihpIgYGRlZmAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBlbnZCb29sKG5hbWU6IHN0cmluZywgZGVmOiBib29sZWFuKTogYm9vbGVhbiB7XG4gIGNvbnN0IHJhdyA9IHByb2Nlc3MuZW52W25hbWVdO1xuICBpZiAocmF3ID09PSB1bmRlZmluZWQgfHwgcmF3LnRyaW0oKSA9PT0gJycpIHJldHVybiBkZWY7XG4gIGNvbnN0IHYgPSByYXcudHJpbSgpLnRvTG93ZXJDYXNlKCk7XG4gIGlmICh2ID09PSAndHJ1ZScgfHwgdiA9PT0gJzEnIHx8IHYgPT09ICd5ZXMnKSByZXR1cm4gdHJ1ZTtcbiAgaWYgKHYgPT09ICdmYWxzZScgfHwgdiA9PT0gJzAnIHx8IHYgPT09ICdubycpIHJldHVybiBmYWxzZTtcbiAgcmV0dXJuIGRlZjtcbn1cblxuLyoqIFJlYWQgYW4gZW52IHZhciBjb25zdHJhaW5lZCB0byBgYWxsb3dlZGA7IHVuc2V0IC8gbm90LWluLXNldCDihpIgYGRlZmAuICovXG5leHBvcnQgZnVuY3Rpb24gZW52RW51bTxUIGV4dGVuZHMgc3RyaW5nPihuYW1lOiBzdHJpbmcsIGFsbG93ZWQ6IHJlYWRvbmx5IFRbXSwgZGVmOiBUKTogVCB7XG4gIGNvbnN0IHJhdyA9IHByb2Nlc3MuZW52W25hbWVdPy50cmltKCkgYXMgVCB8IHVuZGVmaW5lZDtcbiAgcmV0dXJuIHJhdyAhPT0gdW5kZWZpbmVkICYmIGFsbG93ZWQuaW5jbHVkZXMocmF3KSA/IHJhdyA6IGRlZjtcbn1cblxuLyoqIFJlYWQgYSBzdHJpbmcgZW52IHZhcjsgdW5zZXQgLyBibGFuayDihpIgYGRlZmAuICovXG5leHBvcnQgZnVuY3Rpb24gZW52U3RyKG5hbWU6IHN0cmluZywgZGVmOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCByYXcgPSBwcm9jZXNzLmVudltuYW1lXTtcbiAgcmV0dXJuIHJhdyA9PT0gdW5kZWZpbmVkIHx8IHJhdy50cmltKCkgPT09ICcnID8gZGVmIDogcmF3O1xufVxuIl19
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express 5 header type.
|
|
3
|
+
*/
|
|
4
|
+
type HeaderValue = string | string[] | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Extract a single string from a header value that may be string | string[] | undefined.
|
|
7
|
+
*
|
|
8
|
+
* @param value - Header value from Express request
|
|
9
|
+
* @returns First value if array, the value if string, or undefined
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const orgId = getHeaderString(req.headers['x-org-id']);
|
|
14
|
+
* const auth = getHeaderString(req.headers.authorization);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function getHeaderString(value: HeaderValue): string | undefined;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Extract a single string from a header value that may be string | string[] | undefined.
|
|
5
|
+
*
|
|
6
|
+
* @param value - Header value from Express request
|
|
7
|
+
* @returns First value if array, the value if string, or undefined
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const orgId = getHeaderString(req.headers['x-org-id']);
|
|
12
|
+
* const auth = getHeaderString(req.headers.authorization);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function getHeaderString(value) {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value[0];
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVhZGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9oZWFkZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFPdEM7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLFVBQVUsZUFBZSxDQUFDLEtBQWtCO0lBQ2hELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3pCLE9BQU8sS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2xCLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBFeHByZXNzIDUgaGVhZGVyIHR5cGUuXG4gKi9cbnR5cGUgSGVhZGVyVmFsdWUgPSBzdHJpbmcgfCBzdHJpbmdbXSB8IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBFeHRyYWN0IGEgc2luZ2xlIHN0cmluZyBmcm9tIGEgaGVhZGVyIHZhbHVlIHRoYXQgbWF5IGJlIHN0cmluZyB8IHN0cmluZ1tdIHwgdW5kZWZpbmVkLlxuICpcbiAqIEBwYXJhbSB2YWx1ZSAtIEhlYWRlciB2YWx1ZSBmcm9tIEV4cHJlc3MgcmVxdWVzdFxuICogQHJldHVybnMgRmlyc3QgdmFsdWUgaWYgYXJyYXksIHRoZSB2YWx1ZSBpZiBzdHJpbmcsIG9yIHVuZGVmaW5lZFxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCBvcmdJZCA9IGdldEhlYWRlclN0cmluZyhyZXEuaGVhZGVyc1sneC1vcmctaWQnXSk7XG4gKiBjb25zdCBhdXRoID0gZ2V0SGVhZGVyU3RyaW5nKHJlcS5oZWFkZXJzLmF1dGhvcml6YXRpb24pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRIZWFkZXJTdHJpbmcodmFsdWU6IEhlYWRlclZhbHVlKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgcmV0dXJuIHZhbHVlWzBdO1xuICB9XG4gIHJldHVybiB2YWx1ZTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HttpRequest } from '../types/http.js';
|
|
2
|
+
/**
|
|
3
|
+
* Identity information extracted from request headers.
|
|
4
|
+
*/
|
|
5
|
+
export interface RequestIdentity {
|
|
6
|
+
/** Organization ID from x-org-id header */
|
|
7
|
+
readonly orgId?: string;
|
|
8
|
+
/** User ID from x-user-id header */
|
|
9
|
+
readonly userId?: string;
|
|
10
|
+
/** Request ID from x-request-id header */
|
|
11
|
+
readonly requestId?: string;
|
|
12
|
+
/** User role from x-user-role header (decoded from JWT) */
|
|
13
|
+
readonly role?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract identity information from request headers.
|
|
17
|
+
*
|
|
18
|
+
* Extracts common identity headers used for multi-tenant authentication:
|
|
19
|
+
* - x-org-id: Organization identifier
|
|
20
|
+
* - x-user-id: User identifier
|
|
21
|
+
* - x-request-id: Request trace identifier
|
|
22
|
+
* - x-user-role: User role
|
|
23
|
+
*
|
|
24
|
+
* @param req - HTTP request object
|
|
25
|
+
* @returns Identity object with orgId, userId, requestId, and role
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* app.post('/api/resource', requireAuth, async (req, res) => {
|
|
30
|
+
* const identity = getIdentity(req);
|
|
31
|
+
*
|
|
32
|
+
* if (!identity.orgId) {
|
|
33
|
+
* return sendError(res, 400, 'x-org-id header required');
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* // Use identity.orgId, identity.userId, etc.
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function getIdentity(req: HttpRequest): RequestIdentity;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { getHeaderString } from './headers.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extract identity information from request headers.
|
|
6
|
+
*
|
|
7
|
+
* Extracts common identity headers used for multi-tenant authentication:
|
|
8
|
+
* - x-org-id: Organization identifier
|
|
9
|
+
* - x-user-id: User identifier
|
|
10
|
+
* - x-request-id: Request trace identifier
|
|
11
|
+
* - x-user-role: User role
|
|
12
|
+
*
|
|
13
|
+
* @param req - HTTP request object
|
|
14
|
+
* @returns Identity object with orgId, userId, requestId, and role
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* app.post('/api/resource', requireAuth, async (req, res) => {
|
|
19
|
+
* const identity = getIdentity(req);
|
|
20
|
+
*
|
|
21
|
+
* if (!identity.orgId) {
|
|
22
|
+
* return sendError(res, 400, 'x-org-id header required');
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* // Use identity.orgId, identity.userId, etc.
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function getIdentity(req) {
|
|
30
|
+
// Prefer JWT-verified claims (req.user) over raw headers to prevent
|
|
31
|
+
// spoofing. Headers are only used as fallback or for fields not in the
|
|
32
|
+
// JWT (e.g. requestId). The JWT payload uses `sub` for the user id per
|
|
33
|
+
// OIDC convention; that's our authoritative source.
|
|
34
|
+
const user = req.user;
|
|
35
|
+
// Canonical orgId normalization — the SINGLE source of truth. Lowercasing
|
|
36
|
+
// (and trimming) ONCE here guarantees the RLS GUC (`identityScope` reads this
|
|
37
|
+
// raw `identity.orgId`) and the app-layer WHERE clauses (route-wrapper /
|
|
38
|
+
// app-factory, which historically re-lowercased) always agree on tenant. A
|
|
39
|
+
// mismatch — GUC set to `Acme` while WHERE queries `acme` — would, under
|
|
40
|
+
// owner-bypass RLS, silently scope reads to the wrong (or no) tenant. Empty /
|
|
41
|
+
// whitespace-only collapses to undefined so "missing org" stays falsy.
|
|
42
|
+
const rawOrgId = user?.organizationId || getHeaderString(req.headers['x-org-id']);
|
|
43
|
+
const orgId = rawOrgId?.trim().toLowerCase() || undefined;
|
|
44
|
+
return {
|
|
45
|
+
orgId,
|
|
46
|
+
userId: user?.sub || getHeaderString(req.headers['x-user-id']),
|
|
47
|
+
requestId: getHeaderString(req.headers['x-request-id']),
|
|
48
|
+
role: user?.role || getHeaderString(req.headers['x-user-role']),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWRlbnRpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvaWRlbnRpdHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBaUIvQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxHQUFnQjtJQUMxQyxvRUFBb0U7SUFDcEUsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxvREFBb0Q7SUFDcEQsTUFBTSxJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQztJQUN0QiwwRUFBMEU7SUFDMUUsOEVBQThFO0lBQzlFLHlFQUF5RTtJQUN6RSwyRUFBMkU7SUFDM0UseUVBQXlFO0lBQ3pFLDhFQUE4RTtJQUM5RSx1RUFBdUU7SUFDdkUsTUFBTSxRQUFRLEdBQUcsSUFBSSxFQUFFLGNBQWMsSUFBSSxlQUFlLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ2xGLE1BQU0sS0FBSyxHQUFHLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsSUFBSSxTQUFTLENBQUM7SUFDMUQsT0FBTztRQUNMLEtBQUs7UUFDTCxNQUFNLEVBQUUsSUFBSSxFQUFFLEdBQUcsSUFBSSxlQUFlLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUM5RCxTQUFTLEVBQUUsZUFBZSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDdkQsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLElBQUksZUFBZSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLENBQUM7S0FDaEUsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgZ2V0SGVhZGVyU3RyaW5nIH0gZnJvbSAnLi9oZWFkZXJzLmpzJztcbmltcG9ydCB0eXBlIHsgSHR0cFJlcXVlc3QgfSBmcm9tICcuLi90eXBlcy9odHRwLmpzJztcblxuLyoqXG4gKiBJZGVudGl0eSBpbmZvcm1hdGlvbiBleHRyYWN0ZWQgZnJvbSByZXF1ZXN0IGhlYWRlcnMuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVxdWVzdElkZW50aXR5IHtcbiAgLyoqIE9yZ2FuaXphdGlvbiBJRCBmcm9tIHgtb3JnLWlkIGhlYWRlciAqL1xuICByZWFkb25seSBvcmdJZD86IHN0cmluZztcbiAgLyoqIFVzZXIgSUQgZnJvbSB4LXVzZXItaWQgaGVhZGVyICovXG4gIHJlYWRvbmx5IHVzZXJJZD86IHN0cmluZztcbiAgLyoqIFJlcXVlc3QgSUQgZnJvbSB4LXJlcXVlc3QtaWQgaGVhZGVyICovXG4gIHJlYWRvbmx5IHJlcXVlc3RJZD86IHN0cmluZztcbiAgLyoqIFVzZXIgcm9sZSBmcm9tIHgtdXNlci1yb2xlIGhlYWRlciAoZGVjb2RlZCBmcm9tIEpXVCkgKi9cbiAgcmVhZG9ubHkgcm9sZT86IHN0cmluZztcbn1cblxuLyoqXG4gKiBFeHRyYWN0IGlkZW50aXR5IGluZm9ybWF0aW9uIGZyb20gcmVxdWVzdCBoZWFkZXJzLlxuICpcbiAqIEV4dHJhY3RzIGNvbW1vbiBpZGVudGl0eSBoZWFkZXJzIHVzZWQgZm9yIG11bHRpLXRlbmFudCBhdXRoZW50aWNhdGlvbjpcbiAqIC0geC1vcmctaWQ6IE9yZ2FuaXphdGlvbiBpZGVudGlmaWVyXG4gKiAtIHgtdXNlci1pZDogVXNlciBpZGVudGlmaWVyXG4gKiAtIHgtcmVxdWVzdC1pZDogUmVxdWVzdCB0cmFjZSBpZGVudGlmaWVyXG4gKiAtIHgtdXNlci1yb2xlOiBVc2VyIHJvbGVcbiAqXG4gKiBAcGFyYW0gcmVxIC0gSFRUUCByZXF1ZXN0IG9iamVjdFxuICogQHJldHVybnMgSWRlbnRpdHkgb2JqZWN0IHdpdGggb3JnSWQsIHVzZXJJZCwgcmVxdWVzdElkLCBhbmQgcm9sZVxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBhcHAucG9zdCgnL2FwaS9yZXNvdXJjZScsIHJlcXVpcmVBdXRoLCBhc3luYyAocmVxLCByZXMpID0+IHtcbiAqICAgY29uc3QgaWRlbnRpdHkgPSBnZXRJZGVudGl0eShyZXEpO1xuICpcbiAqICAgaWYgKCFpZGVudGl0eS5vcmdJZCkge1xuICogICAgIHJldHVybiBzZW5kRXJyb3IocmVzLCA0MDAsICd4LW9yZy1pZCBoZWFkZXIgcmVxdWlyZWQnKTtcbiAqICAgfVxuICpcbiAqICAgLy8gVXNlIGlkZW50aXR5Lm9yZ0lkLCBpZGVudGl0eS51c2VySWQsIGV0Yy5cbiAqIH0pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRJZGVudGl0eShyZXE6IEh0dHBSZXF1ZXN0KTogUmVxdWVzdElkZW50aXR5IHtcbiAgLy8gUHJlZmVyIEpXVC12ZXJpZmllZCBjbGFpbXMgKHJlcS51c2VyKSBvdmVyIHJhdyBoZWFkZXJzIHRvIHByZXZlbnRcbiAgLy8gc3Bvb2ZpbmcuIEhlYWRlcnMgYXJlIG9ubHkgdXNlZCBhcyBmYWxsYmFjayBvciBmb3IgZmllbGRzIG5vdCBpbiB0aGVcbiAgLy8gSldUIChlLmcuIHJlcXVlc3RJZCkuIFRoZSBKV1QgcGF5bG9hZCB1c2VzIGBzdWJgIGZvciB0aGUgdXNlciBpZCBwZXJcbiAgLy8gT0lEQyBjb252ZW50aW9uOyB0aGF0J3Mgb3VyIGF1dGhvcml0YXRpdmUgc291cmNlLlxuICBjb25zdCB1c2VyID0gcmVxLnVzZXI7XG4gIC8vIENhbm9uaWNhbCBvcmdJZCBub3JtYWxpemF0aW9uIOKAlCB0aGUgU0lOR0xFIHNvdXJjZSBvZiB0cnV0aC4gTG93ZXJjYXNpbmdcbiAgLy8gKGFuZCB0cmltbWluZykgT05DRSBoZXJlIGd1YXJhbnRlZXMgdGhlIFJMUyBHVUMgKGBpZGVudGl0eVNjb3BlYCByZWFkcyB0aGlzXG4gIC8vIHJhdyBgaWRlbnRpdHkub3JnSWRgKSBhbmQgdGhlIGFwcC1sYXllciBXSEVSRSBjbGF1c2VzIChyb3V0ZS13cmFwcGVyIC9cbiAgLy8gYXBwLWZhY3RvcnksIHdoaWNoIGhpc3RvcmljYWxseSByZS1sb3dlcmNhc2VkKSBhbHdheXMgYWdyZWUgb24gdGVuYW50LiBBXG4gIC8vIG1pc21hdGNoIOKAlCBHVUMgc2V0IHRvIGBBY21lYCB3aGlsZSBXSEVSRSBxdWVyaWVzIGBhY21lYCDigJQgd291bGQsIHVuZGVyXG4gIC8vIG93bmVyLWJ5cGFzcyBSTFMsIHNpbGVudGx5IHNjb3BlIHJlYWRzIHRvIHRoZSB3cm9uZyAob3Igbm8pIHRlbmFudC4gRW1wdHkgL1xuICAvLyB3aGl0ZXNwYWNlLW9ubHkgY29sbGFwc2VzIHRvIHVuZGVmaW5lZCBzbyBcIm1pc3Npbmcgb3JnXCIgc3RheXMgZmFsc3kuXG4gIGNvbnN0IHJhd09yZ0lkID0gdXNlcj8ub3JnYW5pemF0aW9uSWQgfHwgZ2V0SGVhZGVyU3RyaW5nKHJlcS5oZWFkZXJzWyd4LW9yZy1pZCddKTtcbiAgY29uc3Qgb3JnSWQgPSByYXdPcmdJZD8udHJpbSgpLnRvTG93ZXJDYXNlKCkgfHwgdW5kZWZpbmVkO1xuICByZXR1cm4ge1xuICAgIG9yZ0lkLFxuICAgIHVzZXJJZDogdXNlcj8uc3ViIHx8IGdldEhlYWRlclN0cmluZyhyZXEuaGVhZGVyc1sneC11c2VyLWlkJ10pLFxuICAgIHJlcXVlc3RJZDogZ2V0SGVhZGVyU3RyaW5nKHJlcS5oZWFkZXJzWyd4LXJlcXVlc3QtaWQnXSksXG4gICAgcm9sZTogdXNlcj8ucm9sZSB8fCBnZXRIZWFkZXJTdHJpbmcocmVxLmhlYWRlcnNbJ3gtdXNlci1yb2xlJ10pLFxuICB9O1xufVxuIl19
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './logger.js';
|
|
2
|
+
export * from './safe-require.js';
|
|
3
|
+
export * from './crash-handlers.js';
|
|
4
|
+
export * from './response.js';
|
|
5
|
+
export * from './params.js';
|
|
6
|
+
export * from './headers.js';
|
|
7
|
+
export * from './identity.js';
|
|
8
|
+
export * from './object.js';
|
|
9
|
+
export * from './alias-resolver.js';
|
|
10
|
+
export * from './concurrency.js';
|
|
11
|
+
export * from './audit.js';
|
|
12
|
+
export * from './secret-encryption.js';
|
|
13
|
+
export * from './metric-emitter.js';
|
|
14
|
+
export * from './aws-scrub.js';
|
|
15
|
+
export * from './ssrf.js';
|
|
16
|
+
export * from './env.js';
|
|
17
|
+
export * from './compliance-attributes.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
export * from './logger.js';
|
|
4
|
+
export * from './safe-require.js';
|
|
5
|
+
export * from './crash-handlers.js';
|
|
6
|
+
export * from './response.js';
|
|
7
|
+
export * from './params.js';
|
|
8
|
+
export * from './headers.js';
|
|
9
|
+
export * from './identity.js';
|
|
10
|
+
export * from './object.js';
|
|
11
|
+
export * from './alias-resolver.js';
|
|
12
|
+
export * from './concurrency.js';
|
|
13
|
+
export * from './audit.js';
|
|
14
|
+
export * from './secret-encryption.js';
|
|
15
|
+
export * from './metric-emitter.js';
|
|
16
|
+
export * from './aws-scrub.js';
|
|
17
|
+
export * from './ssrf.js';
|
|
18
|
+
export * from './env.js';
|
|
19
|
+
export * from './compliance-attributes.js';
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsNEJBQTRCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuZXhwb3J0ICogZnJvbSAnLi9sb2dnZXIuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9zYWZlLXJlcXVpcmUuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9jcmFzaC1oYW5kbGVycy5qcyc7XG5leHBvcnQgKiBmcm9tICcuL3Jlc3BvbnNlLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vcGFyYW1zLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vaGVhZGVycy5qcyc7XG5leHBvcnQgKiBmcm9tICcuL2lkZW50aXR5LmpzJztcbmV4cG9ydCAqIGZyb20gJy4vb2JqZWN0LmpzJztcbmV4cG9ydCAqIGZyb20gJy4vYWxpYXMtcmVzb2x2ZXIuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9jb25jdXJyZW5jeS5qcyc7XG5leHBvcnQgKiBmcm9tICcuL2F1ZGl0LmpzJztcbmV4cG9ydCAqIGZyb20gJy4vc2VjcmV0LWVuY3J5cHRpb24uanMnO1xuZXhwb3J0ICogZnJvbSAnLi9tZXRyaWMtZW1pdHRlci5qcyc7XG5leHBvcnQgKiBmcm9tICcuL2F3cy1zY3J1Yi5qcyc7XG5leHBvcnQgKiBmcm9tICcuL3NzcmYuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9lbnYuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9jb21wbGlhbmNlLWF0dHJpYnV0ZXMuanMnO1xuIl19
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
/**
|
|
3
|
+
* Redact sensitive values from an ARBITRARY payload using the same key-pattern
|
|
4
|
+
* rules the Winston `redactFormat` applies to log metadata. Use this for
|
|
5
|
+
* payloads that reach an output channel OUTSIDE the logger — e.g. an SSE frame
|
|
6
|
+
* pushed straight to a client — so a `password`/`token`/`secret` field is
|
|
7
|
+
* masked there too. A top-level sensitive key is masked; nested objects/arrays
|
|
8
|
+
* are walked (depth-capped) exactly as in the logger. Returns a redacted COPY;
|
|
9
|
+
* primitives pass through unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export declare function redactSensitive(value: unknown): unknown;
|
|
12
|
+
/**
|
|
13
|
+
* Create a logger instance for a service.
|
|
14
|
+
*
|
|
15
|
+
* When LOG_FORMAT=json (default), outputs structured JSON for Loki ingestion:
|
|
16
|
+
* {"level":"info","message":"Server started","service":"pipeline","timestamp":"..."}
|
|
17
|
+
*
|
|
18
|
+
* When LOG_FORMAT=text, outputs colorized human-readable format:
|
|
19
|
+
* 2026-02-13T10:30:00.000Z info [pipeline] Server started
|
|
20
|
+
*
|
|
21
|
+
* @param serviceName - Name of the service for log identification
|
|
22
|
+
* @returns Configured Winston logger instance
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { createLogger } from '@pipeline-builder/api-core';
|
|
27
|
+
*
|
|
28
|
+
* const logger = createLogger('get-plugin');
|
|
29
|
+
* logger.info('Server started', { port: 3000 });
|
|
30
|
+
* logger.error('Database error', { error: err.message });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function createLogger(serviceName: string): winston.Logger;
|
|
34
|
+
/**
|
|
35
|
+
* Default logger instance (service name from SERVICE_NAME env var or 'api').
|
|
36
|
+
*/
|
|
37
|
+
export declare const logger: winston.Logger;
|
|
38
|
+
export default logger;
|