Skip to content

Commit 0ba97a8

Browse files
committed
fix(onboard): accept managed runtime aliases
Signed-off-by: Deepak Jain <deepujain@gmail.com>
1 parent b09eece commit 0ba97a8

2 files changed

Lines changed: 145 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { describe, expect, it } from "vitest";
5+
import { managedStartupE2eProfile } from "../../../scripts/checks/generate-managed-startup-profile-fixture.mts";
6+
import {
7+
type ManagedStartupJsonObject,
8+
type ManagedStartupProfile,
9+
validateManagedStartupProfile,
10+
} from "./managed-startup/profile.ts";
11+
12+
const SLACK_BOT_ALIAS = {
13+
envKey: "SLACK_BOT_TOKEN",
14+
match: "^openshell:resolve:env:(v[0-9]+_)?SLACK_BOT_TOKEN$",
15+
value: "xoxb-OPENSHELL-RESOLVE-ENV-SLACK_BOT_TOKEN",
16+
} as const;
17+
18+
const SLACK_APP_ALIAS = {
19+
envKey: "SLACK_APP_TOKEN",
20+
match: "^openshell:resolve:env:(v[0-9]+_)?SLACK_APP_TOKEN$",
21+
value: "xapp-OPENSHELL-RESOLVE-ENV-SLACK_APP_TOKEN",
22+
} as const;
23+
24+
function profileWithAliases(
25+
aliases: readonly ManagedStartupJsonObject[],
26+
): ManagedStartupProfile {
27+
const profile = managedStartupE2eProfile("hermes");
28+
return {
29+
...profile,
30+
messaging: {
31+
plan: {
32+
schemaVersion: 1,
33+
agent: "hermes",
34+
runtimeSetup: {
35+
nodePreloads: [],
36+
envAliases: aliases.map((alias) => ({ channelId: "slack", ...alias })),
37+
secretScans: [],
38+
},
39+
},
40+
},
41+
};
42+
}
43+
44+
describe("managed startup runtime aliases", () => {
45+
it("accepts the stock Slack runtime aliases (#9397)", () => {
46+
expect(() =>
47+
validateManagedStartupProfile(profileWithAliases([SLACK_BOT_ALIAS, SLACK_APP_ALIAS])),
48+
).not.toThrow();
49+
});
50+
51+
it.each([
52+
["an invalid environment key", { ...SLACK_BOT_ALIAS, envKey: "BAD KEY" }],
53+
[
54+
"an unanchored resolver expression",
55+
{ ...SLACK_BOT_ALIAS, match: "openshell:resolve:env:SLACK_BOT_TOKEN" },
56+
],
57+
[
58+
"a resolver expression for another key",
59+
{
60+
...SLACK_BOT_ALIAS,
61+
match: "^openshell:resolve:env:(v[0-9]+_)?SLACK_APP_TOKEN$",
62+
},
63+
],
64+
[
65+
"a placeholder for another key",
66+
{ ...SLACK_BOT_ALIAS, value: "xoxb-OPENSHELL-RESOLVE-ENV-SLACK_APP_TOKEN" },
67+
],
68+
["a raw credential", { ...SLACK_BOT_ALIAS, value: `xoxb-${"a".repeat(32)}` }],
69+
])("rejects %s (#9397)", (_label, alias) => {
70+
expect(() => validateManagedStartupProfile(profileWithAliases([alias]))).toThrow(
71+
/credential-shaped string data/,
72+
);
73+
});
74+
75+
it.each([SLACK_BOT_ALIAS.match, SLACK_BOT_ALIAS.value])(
76+
"rejects runtime alias data outside the schema-owned path (#9397)",
77+
(runtimeAliasData) => {
78+
const profile = profileWithAliases([]);
79+
expect(() =>
80+
validateManagedStartupProfile({
81+
...profile,
82+
messaging: {
83+
plan: {
84+
...profile.messaging.plan,
85+
note: runtimeAliasData,
86+
},
87+
},
88+
}),
89+
).toThrow(/credential-shaped string data/);
90+
},
91+
);
92+
});

src/lib/onboard/managed-startup/profile.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,54 @@ function isMessagingCredentialPlaceholderAssignment(
10491049
return CREDENTIAL_ENV_NAME_PATTERN.test(envKey) && envKey === placeholderEnvKey;
10501050
}
10511051

1052+
function isMessagingRuntimeEnvAliasPath(path: readonly string[]): boolean {
1053+
return (
1054+
path.length === 5 &&
1055+
path[0] === "messaging" &&
1056+
path[1] === "plan" &&
1057+
path[2] === "runtimeSetup" &&
1058+
path[3] === "envAliases" &&
1059+
JSON_ARRAY_INDEX_SEGMENT_RE.test(path[4] ?? "")
1060+
);
1061+
}
1062+
1063+
function ownDataPropertyValue(value: Record<string, unknown>, key: string): unknown {
1064+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
1065+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
1066+
}
1067+
1068+
function isCanonicalMessagingRuntimeEnvAlias(
1069+
path: readonly string[],
1070+
value: Record<string, unknown>,
1071+
): boolean {
1072+
if (!isMessagingRuntimeEnvAliasPath(path)) return false;
1073+
const envKey = ownDataPropertyValue(value, "envKey");
1074+
const match = ownDataPropertyValue(value, "match");
1075+
const placeholder = ownDataPropertyValue(value, "value");
1076+
return (
1077+
typeof envKey === "string" &&
1078+
CREDENTIAL_ENV_NAME_PATTERN.test(envKey) &&
1079+
match === `^openshell:resolve:env:(v[0-9]+_)?${envKey}$` &&
1080+
typeof placeholder === "string" &&
1081+
messagingCredentialPlaceholderEnvKey(placeholder) === envKey
1082+
);
1083+
}
1084+
1085+
function isAllowedMessagingRuntimeAliasStringPath(
1086+
path: readonly string[],
1087+
allowedAliasIndexes: ReadonlySet<string>,
1088+
): boolean {
1089+
return (
1090+
path.length === 6 &&
1091+
path[0] === "messaging" &&
1092+
path[1] === "plan" &&
1093+
path[2] === "runtimeSetup" &&
1094+
path[3] === "envAliases" &&
1095+
allowedAliasIndexes.has(path[4] ?? "") &&
1096+
(path[5] === "match" || path[5] === "value")
1097+
);
1098+
}
1099+
10521100
function isMessagingPackagePin(path: readonly string[], value: unknown): boolean {
10531101
return (
10541102
path.length === 6 &&
@@ -1399,6 +1447,7 @@ function assertPayloadStructureAndCredentialShapes(root: unknown): void {
13991447
depth: number;
14001448
path: readonly string[];
14011449
}> = [{ value: root, depth: 0, path: [] }];
1450+
const allowedRuntimeAliasIndexes = new Set<string>();
14021451
let discoveredNodes = 1;
14031452
let observedBytes = 0;
14041453

@@ -1426,6 +1475,7 @@ function assertPayloadStructureAndCredentialShapes(root: unknown): void {
14261475
if (typeof current.value === "string") {
14271476
observeText(current.value);
14281477
if (
1478+
!isAllowedMessagingRuntimeAliasStringPath(current.path, allowedRuntimeAliasIndexes) &&
14291479
!isMessagingCredentialPlaceholder(current.path, current.value) &&
14301480
!isMessagingCredentialPlaceholderAssignment(current.path, current.value) &&
14311481
(valueLooksLikeSecret(current.value) ||
@@ -1485,6 +1535,9 @@ function assertPayloadStructureAndCredentialShapes(root: unknown): void {
14851535
if ("toJSON" in current.value) {
14861536
invalid("payload must not define a custom JSON serializer");
14871537
}
1538+
if (isCanonicalMessagingRuntimeEnvAlias(current.path, current.value)) {
1539+
allowedRuntimeAliasIndexes.add(current.path[4] as string);
1540+
}
14881541
const keys = Object.getOwnPropertyNames(current.value);
14891542
if (
14901543
Object.getOwnPropertySymbols(current.value).length > 0 ||

0 commit comments

Comments
 (0)