@checkstack/backend-api 0.7.0 → 0.8.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/CHANGELOG.md +45 -0
- package/package.json +5 -5
- package/src/base-strategy-config.ts +26 -0
- package/src/health-check.ts +13 -38
- package/src/index.ts +1 -0
- package/src/rpc.ts +21 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @checkstack/backend-api
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0ebbe56: Security Vulnerability Remediation completed:
|
|
8
|
+
- Refactored core authorization to Fail-Closed architecture with secure defaults.
|
|
9
|
+
- Implemented `assertTeamManagementAccess` to resolve BOLA in Teams Management.
|
|
10
|
+
- Protected internal S2S capabilities via explicit wildcard `serviceScope` definitions.
|
|
11
|
+
- Disarmed OS Command Injection in DiskCollector via strict regex validation and bash escaping.
|
|
12
|
+
- Re-architected inline script processing executing scripts in sandboxed Web Worker contexts.
|
|
13
|
+
- Isolated subprocess environment scopes in PingStrategy limiting variable leakage.
|
|
14
|
+
- Enforced strict token/API Key parsing with URLSearchParams checking.
|
|
15
|
+
- Explicitly fail-fast on missing DATABASE_URL configuration across independent backend clusters.
|
|
16
|
+
- Activated strict HTTP Security Headers (HSTS, CSP, X-Frame-Options) across the API automatically.
|
|
17
|
+
- Updated dependencies [0ebbe56]
|
|
18
|
+
- @checkstack/common@0.6.3
|
|
19
|
+
- @checkstack/queue-api@0.2.6
|
|
20
|
+
- @checkstack/healthcheck-common@0.8.3
|
|
21
|
+
- @checkstack/signal-common@0.1.7
|
|
22
|
+
|
|
23
|
+
## 0.8.0
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- 869b4ab: ## Health Check Execution Improvements
|
|
28
|
+
|
|
29
|
+
### Breaking Changes (backend-api)
|
|
30
|
+
|
|
31
|
+
- `HealthCheckStrategy.createClient()` now accepts `unknown` instead of `TConfig` due to TypeScript contravariance constraints. Implementations should use `this.config.validate(config)` to narrow the type.
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
- **Platform-level hard timeout**: The executor now wraps the entire health check execution (connection + all collectors) in a single timeout, ensuring checks never hang indefinitely.
|
|
36
|
+
- **Parallel collector execution**: Collectors now run in parallel using `Promise.allSettled()`, improving performance while ensuring all collectors complete regardless of individual failures.
|
|
37
|
+
- **Base strategy config schema**: All strategy configs now extend `baseStrategyConfigSchema` which provides a standardized `timeout` field with sensible defaults (30s, min 100ms).
|
|
38
|
+
|
|
39
|
+
### Fixes
|
|
40
|
+
|
|
41
|
+
- Fixed HTTP and Jenkins strategies clearing timeouts before reading the full response body.
|
|
42
|
+
- Simplified registry type signatures by using default type parameters.
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- @checkstack/queue-api@0.2.5
|
|
47
|
+
|
|
3
48
|
## 0.7.0
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/backend-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/common": "0.6.
|
|
13
|
-
"@checkstack/healthcheck-common": "0.8.
|
|
14
|
-
"@checkstack/queue-api": "0.2.
|
|
15
|
-
"@checkstack/signal-common": "0.1.
|
|
12
|
+
"@checkstack/common": "0.6.2",
|
|
13
|
+
"@checkstack/healthcheck-common": "0.8.2",
|
|
14
|
+
"@checkstack/queue-api": "0.2.5",
|
|
15
|
+
"@checkstack/signal-common": "0.1.6",
|
|
16
16
|
"@orpc/client": "^1.13.2",
|
|
17
17
|
"@orpc/openapi": "^1.13.2",
|
|
18
18
|
"@orpc/server": "^1.13.2",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { configNumber } from "./zod-config";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base configuration schema that all strategy configs should extend.
|
|
6
|
+
* Provides the required `timeout` field with a sensible default.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const myConfigSchema = baseStrategyConfigSchema.extend({
|
|
11
|
+
* host: z.string().describe("Server hostname"),
|
|
12
|
+
* port: z.number().default(22),
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const baseStrategyConfigSchema = z.object({
|
|
17
|
+
timeout: configNumber({})
|
|
18
|
+
.min(100)
|
|
19
|
+
.default(30_000)
|
|
20
|
+
.describe("Execution timeout in milliseconds"),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base config type that all strategy configs must satisfy.
|
|
25
|
+
*/
|
|
26
|
+
export type BaseStrategyConfig = z.infer<typeof baseStrategyConfigSchema>;
|
package/src/health-check.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
VersionedAggregated,
|
|
5
5
|
AggregatedResultShape,
|
|
6
6
|
} from "./aggregated-result";
|
|
7
|
+
import type { BaseStrategyConfig } from "./base-strategy-config";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Health check result with typed metadata.
|
|
@@ -31,7 +32,7 @@ export interface HealthCheckRunForAggregation<
|
|
|
31
32
|
* Connected transport client with cleanup capability.
|
|
32
33
|
*/
|
|
33
34
|
export interface ConnectedClient<
|
|
34
|
-
TClient extends TransportClient<
|
|
35
|
+
TClient extends TransportClient<never, unknown>,
|
|
35
36
|
> {
|
|
36
37
|
/** The connected transport client */
|
|
37
38
|
client: TClient;
|
|
@@ -46,18 +47,18 @@ export interface ConnectedClient<
|
|
|
46
47
|
* and returns a transport client. The platform executor handles running
|
|
47
48
|
* collectors and basic health check logic (connectivity test, latency measurement).
|
|
48
49
|
*
|
|
49
|
-
* @template TConfig - Configuration type for this strategy
|
|
50
|
+
* @template TConfig - Configuration type for this strategy (must include timeout)
|
|
50
51
|
* @template TClient - Transport client type (e.g., SshTransportClient)
|
|
51
52
|
* @template TResult - Per-run result type (for aggregation)
|
|
52
53
|
* @template TAggregatedFields - Aggregated field definitions for VersionedAggregated
|
|
53
54
|
*/
|
|
54
55
|
export interface HealthCheckStrategy<
|
|
55
|
-
TConfig =
|
|
56
|
-
TClient extends TransportClient<
|
|
57
|
-
|
|
56
|
+
TConfig extends BaseStrategyConfig = BaseStrategyConfig,
|
|
57
|
+
TClient extends TransportClient<never, unknown> = TransportClient<
|
|
58
|
+
never,
|
|
58
59
|
unknown
|
|
59
60
|
>,
|
|
60
|
-
TResult =
|
|
61
|
+
TResult = unknown,
|
|
61
62
|
TAggregatedFields extends AggregatedResultShape = AggregatedResultShape,
|
|
62
63
|
> {
|
|
63
64
|
id: string;
|
|
@@ -77,11 +78,11 @@ export interface HealthCheckStrategy<
|
|
|
77
78
|
* Create a connected transport client from the configuration.
|
|
78
79
|
* The platform will use this client to execute collectors.
|
|
79
80
|
*
|
|
80
|
-
* @param config -
|
|
81
|
+
* @param config - Strategy configuration (use config.validate() to narrow type)
|
|
81
82
|
* @returns Connected client wrapper with close() method
|
|
82
83
|
* @throws Error if connection fails (will be caught by executor)
|
|
83
84
|
*/
|
|
84
|
-
createClient(config:
|
|
85
|
+
createClient(config: unknown): Promise<ConnectedClient<TClient>>;
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
88
|
* Incrementally merge new run data into an existing aggregate.
|
|
@@ -103,41 +104,15 @@ export interface HealthCheckStrategy<
|
|
|
103
104
|
* A registered strategy with its owning plugin metadata and qualified ID.
|
|
104
105
|
*/
|
|
105
106
|
export interface RegisteredStrategy {
|
|
106
|
-
strategy: HealthCheckStrategy
|
|
107
|
-
unknown,
|
|
108
|
-
TransportClient<unknown, unknown>,
|
|
109
|
-
unknown,
|
|
110
|
-
AggregatedResultShape
|
|
111
|
-
>;
|
|
107
|
+
strategy: HealthCheckStrategy;
|
|
112
108
|
ownerPluginId: string;
|
|
113
109
|
qualifiedId: string;
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
export interface HealthCheckRegistry {
|
|
117
|
-
register(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
TransportClient<unknown, unknown>,
|
|
121
|
-
unknown,
|
|
122
|
-
AggregatedResultShape
|
|
123
|
-
>,
|
|
124
|
-
): void;
|
|
125
|
-
getStrategy(
|
|
126
|
-
id: string,
|
|
127
|
-
):
|
|
128
|
-
| HealthCheckStrategy<
|
|
129
|
-
unknown,
|
|
130
|
-
TransportClient<unknown, unknown>,
|
|
131
|
-
unknown,
|
|
132
|
-
AggregatedResultShape
|
|
133
|
-
>
|
|
134
|
-
| undefined;
|
|
135
|
-
getStrategies(): HealthCheckStrategy<
|
|
136
|
-
unknown,
|
|
137
|
-
TransportClient<unknown, unknown>,
|
|
138
|
-
unknown,
|
|
139
|
-
AggregatedResultShape
|
|
140
|
-
>[];
|
|
113
|
+
register<S extends HealthCheckStrategy>(strategy: S): void;
|
|
114
|
+
getStrategy(id: string): HealthCheckStrategy | undefined;
|
|
115
|
+
getStrategies(): HealthCheckStrategy[];
|
|
141
116
|
/**
|
|
142
117
|
* Get all registered strategies with their metadata (qualified ID, owner plugin).
|
|
143
118
|
*/
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./extension-point";
|
|
|
3
3
|
export * from "./core-services";
|
|
4
4
|
export * from "./plugin-system";
|
|
5
5
|
export * from "./health-check";
|
|
6
|
+
export * from "./base-strategy-config";
|
|
6
7
|
export * from "./auth-strategy";
|
|
7
8
|
export * from "./zod-config";
|
|
8
9
|
export * from "./encryption";
|
package/src/rpc.ts
CHANGED
|
@@ -206,6 +206,23 @@ export const autoAuthMiddleware = os.middleware(
|
|
|
206
206
|
|
|
207
207
|
// 5. Skip remaining checks for services - they are trusted
|
|
208
208
|
if (user?.type === "service") {
|
|
209
|
+
// SECURITY: Check service-level scope restrictions
|
|
210
|
+
const serviceScope = meta?.serviceScope;
|
|
211
|
+
if (serviceScope && serviceScope.length > 0) {
|
|
212
|
+
const isAllowed = serviceScope.some((allowedPattern) => {
|
|
213
|
+
if (allowedPattern.endsWith("*")) {
|
|
214
|
+
const prefix = allowedPattern.slice(0, -1);
|
|
215
|
+
return user.pluginId.startsWith(prefix);
|
|
216
|
+
}
|
|
217
|
+
return allowedPattern === user.pluginId;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!isAllowed) {
|
|
221
|
+
throw new ORPCError("FORBIDDEN", {
|
|
222
|
+
message: `Service '${user.pluginId}' is not allowed to call this endpoint`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
209
226
|
return next({});
|
|
210
227
|
}
|
|
211
228
|
|
|
@@ -483,8 +500,8 @@ async function checkResourceAccessViaS2S({
|
|
|
483
500
|
});
|
|
484
501
|
return result.hasAccess;
|
|
485
502
|
} catch {
|
|
486
|
-
//
|
|
487
|
-
return
|
|
503
|
+
// SECURITY: Fail-Closed — deny access when S2S check fails
|
|
504
|
+
return false;
|
|
488
505
|
}
|
|
489
506
|
}
|
|
490
507
|
|
|
@@ -520,8 +537,8 @@ async function getAccessibleResourceIdsViaS2S({
|
|
|
520
537
|
hasGlobalAccess,
|
|
521
538
|
});
|
|
522
539
|
} catch {
|
|
523
|
-
//
|
|
524
|
-
return
|
|
540
|
+
// SECURITY: Fail-Closed — return empty set when S2S check fails
|
|
541
|
+
return [];
|
|
525
542
|
}
|
|
526
543
|
}
|
|
527
544
|
|