@appforge-ci/core 0.3.0 → 0.3.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/dist/agent-readiness.d.ts +11 -3
- package/dist/agent-readiness.d.ts.map +1 -1
- package/dist/agent-readiness.js +15 -5
- package/dist/agent-readiness.js.map +1 -1
- package/dist/agent-readiness.test.js +34 -10
- package/dist/agent-readiness.test.js.map +1 -1
- package/dist/api-client.d.ts +39 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +52 -0
- package/dist/api-client.js.map +1 -1
- package/dist/appforge-verify.test.d.ts +2 -0
- package/dist/appforge-verify.test.d.ts.map +1 -0
- package/dist/appforge-verify.test.js +958 -0
- package/dist/appforge-verify.test.js.map +1 -0
- package/dist/attestation-keys.d.ts +20 -0
- package/dist/attestation-keys.d.ts.map +1 -0
- package/dist/attestation-keys.js +27 -0
- package/dist/attestation-keys.js.map +1 -0
- package/dist/attestation-keys.test.d.ts +2 -0
- package/dist/attestation-keys.test.d.ts.map +1 -0
- package/dist/attestation-keys.test.js +45 -0
- package/dist/attestation-keys.test.js.map +1 -0
- package/dist/attestation-signing.d.ts +226 -0
- package/dist/attestation-signing.d.ts.map +1 -0
- package/dist/attestation-signing.js +276 -0
- package/dist/attestation-signing.js.map +1 -0
- package/dist/attestation-signing.test.d.ts +2 -0
- package/dist/attestation-signing.test.d.ts.map +1 -0
- package/dist/attestation-signing.test.js +250 -0
- package/dist/attestation-signing.test.js.map +1 -0
- package/dist/attestation-workerd.test.d.ts +2 -0
- package/dist/attestation-workerd.test.d.ts.map +1 -0
- package/dist/attestation-workerd.test.js +183 -0
- package/dist/attestation-workerd.test.js.map +1 -0
- package/dist/attestation.d.ts +169 -22
- package/dist/attestation.d.ts.map +1 -1
- package/dist/attestation.js +42 -16
- package/dist/attestation.js.map +1 -1
- package/dist/build-vpn.d.ts +11 -1
- package/dist/build-vpn.d.ts.map +1 -1
- package/dist/build-vpn.js +27 -0
- package/dist/build-vpn.js.map +1 -1
- package/dist/fleet-vpn.d.ts +64 -0
- package/dist/fleet-vpn.d.ts.map +1 -0
- package/dist/fleet-vpn.js +88 -0
- package/dist/fleet-vpn.js.map +1 -0
- package/dist/fleet-vpn.test.d.ts +2 -0
- package/dist/fleet-vpn.test.d.ts.map +1 -0
- package/dist/fleet-vpn.test.js +69 -0
- package/dist/fleet-vpn.test.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas-repo-url.test.d.ts +2 -0
- package/dist/schemas-repo-url.test.d.ts.map +1 -0
- package/dist/schemas-repo-url.test.js +67 -0
- package/dist/schemas-repo-url.test.js.map +1 -0
- package/dist/schemas.d.ts +18 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +56 -10
- package/dist/schemas.js.map +1 -1
- package/dist/ssh-deploy-key.d.ts +59 -0
- package/dist/ssh-deploy-key.d.ts.map +1 -0
- package/dist/ssh-deploy-key.js +124 -0
- package/dist/ssh-deploy-key.js.map +1 -0
- package/dist/ssh-deploy-key.test.d.ts +2 -0
- package/dist/ssh-deploy-key.test.d.ts.map +1 -0
- package/dist/ssh-deploy-key.test.js +117 -0
- package/dist/ssh-deploy-key.test.js.map +1 -0
- package/dist/types.d.ts +57 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +1 -1
- package/dist/vpn-attestation-workerd.test.d.ts +2 -0
- package/dist/vpn-attestation-workerd.test.d.ts.map +1 -0
- package/dist/vpn-attestation-workerd.test.js +184 -0
- package/dist/vpn-attestation-workerd.test.js.map +1 -0
- package/dist/vpn-attestation.d.ts +1015 -0
- package/dist/vpn-attestation.d.ts.map +1 -0
- package/dist/vpn-attestation.js +963 -0
- package/dist/vpn-attestation.js.map +1 -0
- package/dist/vpn-attestation.test.d.ts +2 -0
- package/dist/vpn-attestation.test.d.ts.map +1 -0
- package/dist/vpn-attestation.test.js +613 -0
- package/dist/vpn-attestation.test.js.map +1 -0
- package/dist/vpn-egress-presets.d.ts +1 -1
- package/dist/vpn-egress-presets.d.ts.map +1 -1
- package/dist/vpn-egress-presets.js +2 -2
- package/dist/vpn-egress-presets.js.map +1 -1
- package/dist/vpn-sandbox.d.ts +67 -0
- package/dist/vpn-sandbox.d.ts.map +1 -0
- package/dist/vpn-sandbox.js +22 -0
- package/dist/vpn-sandbox.js.map +1 -0
- package/dist/vpn-sandbox.test.d.ts +2 -0
- package/dist/vpn-sandbox.test.d.ts.map +1 -0
- package/dist/vpn-sandbox.test.js +34 -0
- package/dist/vpn-sandbox.test.js.map +1 -0
- package/dist/vpn-ssh.d.ts +93 -0
- package/dist/vpn-ssh.d.ts.map +1 -1
- package/dist/vpn-ssh.js +213 -1
- package/dist/vpn-ssh.js.map +1 -1
- package/dist/vpn-ssh.test.js +200 -1
- package/dist/vpn-ssh.test.js.map +1 -1
- package/package.json +1 -1
package/dist/build-vpn.js
CHANGED
|
@@ -20,4 +20,31 @@ export function describeBuildVpn(vpn) {
|
|
|
20
20
|
return "VPN status unknown: no VPN events were recorded for this build";
|
|
21
21
|
return "No VPN";
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* One-line, human-readable state of a build's attestation, shared by the dashboard and `appforge build show`. Says plainly when it
|
|
25
|
+
* is not (yet) evidence: pending, degraded, unsigned, or recording a violation.
|
|
26
|
+
* "Evidence: complete, signed, all controls enforced"
|
|
27
|
+
* "Evidence: complete, signed, 2 control violations recorded"
|
|
28
|
+
* "Evidence: DEGRADED (the agent did not report), signed: server-observed facts only"
|
|
29
|
+
* "Evidence: complete, NOT SIGNED (the platform had no signing key)"
|
|
30
|
+
* "Evidence: pending (the build's agent has not reported yet)"
|
|
31
|
+
*/
|
|
32
|
+
export function describeBuildAttestation(a) {
|
|
33
|
+
if (!a)
|
|
34
|
+
return null;
|
|
35
|
+
if (a.state === "pending")
|
|
36
|
+
return "Evidence: pending (the build's agent has not reported yet)";
|
|
37
|
+
const signed = a.signed ? "signed" : "NOT SIGNED (the platform had no signing key)";
|
|
38
|
+
if (a.state === "degraded_server_observed")
|
|
39
|
+
return `Evidence: DEGRADED (the agent did not report), ${signed}: server-observed facts only`;
|
|
40
|
+
const verdict = a.violations > 0
|
|
41
|
+
? `${a.violations} control violation${a.violations === 1 ? "" : "s"} recorded`
|
|
42
|
+
: a.controlsVerdict === "all_enforced"
|
|
43
|
+
? "all controls enforced"
|
|
44
|
+
: a.controlsVerdict === "network_policy_checked"
|
|
45
|
+
? "network policy checked"
|
|
46
|
+
: "no control verdict";
|
|
47
|
+
const partial = a.completeness === "partial" ? ", partial" : "";
|
|
48
|
+
return `Evidence: complete${partial}, ${signed}, ${verdict}`;
|
|
49
|
+
}
|
|
23
50
|
//# sourceMappingURL=build-vpn.js.map
|
package/dist/build-vpn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-vpn.js","sourceRoot":"","sources":["../src/build-vpn.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAA+B;IAC9D,IAAI,CAAC,GAAG;QAAE,OAAO,0BAA0B,CAAC;IAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,IAAI,QAAQ,CAAC;IACxC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,IAAI,GAAG,CAAC,OAAO,KAAK,WAAW;YAAE,OAAO,qBAAqB,IAAI,cAAc,CAAC;QAChF,IAAI,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,sBAAsB,GAAG,CAAC,MAAM,IAAI,gCAAgC,EAAE,CAAC;QAC5G,OAAO,kBAAkB,IAAI,oCAAoC,CAAC;IACpE,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,gEAAgE,CAAC;IACvG,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
1
|
+
{"version":3,"file":"build-vpn.js","sourceRoot":"","sources":["../src/build-vpn.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAA+B;IAC9D,IAAI,CAAC,GAAG;QAAE,OAAO,0BAA0B,CAAC;IAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,IAAI,QAAQ,CAAC;IACxC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,IAAI,GAAG,CAAC,OAAO,KAAK,WAAW;YAAE,OAAO,qBAAqB,IAAI,cAAc,CAAC;QAChF,IAAI,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,sBAAsB,GAAG,CAAC,MAAM,IAAI,gCAAgC,EAAE,CAAC;QAC5G,OAAO,kBAAkB,IAAI,oCAAoC,CAAC;IACpE,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,gEAAgE,CAAC;IACvG,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,CAAqC;IAC5E,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,4DAA4D,CAAC;IAC/F,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,8CAA8C,CAAC;IACpF,IAAI,CAAC,CAAC,KAAK,KAAK,0BAA0B;QAAE,OAAO,kDAAkD,MAAM,8BAA8B,CAAC;IAC1I,MAAM,OAAO,GACX,CAAC,CAAC,UAAU,GAAG,CAAC;QACd,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,qBAAqB,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW;QAC9E,CAAC,CAAC,CAAC,CAAC,eAAe,KAAK,cAAc;YACpC,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,CAAC,CAAC,eAAe,KAAK,wBAAwB;gBAC9C,CAAC,CAAC,wBAAwB;gBAC1B,CAAC,CAAC,oBAAoB,CAAC;IAC/B,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,OAAO,qBAAqB,OAAO,KAAK,MAAM,KAAK,OAAO,EAAE,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type EnforcementLevel } from "./agent-readiness.js";
|
|
2
|
+
import type { AgentVpnPin } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* How the fleet shows an agent's VPN facts, shared by the dashboard's Fleet page
|
|
5
|
+
* and `appforge-admin agents list` so the two surfaces never disagree about what
|
|
6
|
+
* a feature id means or what "eligible" is. Pure functions and constants only.
|
|
7
|
+
*
|
|
8
|
+
* Honesty rule: every sentence here describes what the CONTROL PLANE knows, i.e.
|
|
9
|
+
* what the Mac reported at its last heartbeat. A feature chip is a claim the
|
|
10
|
+
* Mac made, not a proof; the scheduler only trusts it while the heartbeat is
|
|
11
|
+
* fresh (`agentVpnCapabilitiesCurrent`), and the page says so.
|
|
12
|
+
*/
|
|
13
|
+
/** What each VPN feature id means, as a one-sentence legend. */
|
|
14
|
+
export declare const VPN_FEATURE_LEGEND: Readonly<Record<string, string>>;
|
|
15
|
+
/** Legend sentence for a feature id; a short fallback for an id this build does not know. */
|
|
16
|
+
export declare function describeVpnFeature(id: string): string;
|
|
17
|
+
/** What each enforcement level means. */
|
|
18
|
+
export declare const ENFORCEMENT_LEVEL_LEGEND: Readonly<Record<EnforcementLevel, string>>;
|
|
19
|
+
export declare function describeEnforcementLevel(level: unknown): string;
|
|
20
|
+
/**
|
|
21
|
+
* The VPN eligibility as the scheduler applies it in `/agents/lease-job`: a Mac
|
|
22
|
+
* serves VPN builds only when it reports the verified helper (`vpnCapable`), a
|
|
23
|
+
* platform admin approved it (`vpnApproved`), AND the scheduler currently trusts
|
|
24
|
+
* the helper report (`agentVpnCapabilitiesCurrent`: a VPN-capable agent version
|
|
25
|
+
* with a recent heartbeat). `vpnCapable` is sticky between heartbeats, so an
|
|
26
|
+
* offline or too-old Mac can still carry helper=1 + approved=1 yet is never
|
|
27
|
+
* leased a VPN build; `stale` is true for exactly that case.
|
|
28
|
+
*
|
|
29
|
+
* `agentVersion` and `lastSeenAt` are optional only for callers that cannot
|
|
30
|
+
* supply them (an older API omits them): when either is absent freshness is
|
|
31
|
+
* unknown and only the helper and approval halves are judged.
|
|
32
|
+
*/
|
|
33
|
+
export declare function vpnEligibility(a: {
|
|
34
|
+
vpnCapable?: number;
|
|
35
|
+
vpnApproved?: number;
|
|
36
|
+
agentVersion?: string | null;
|
|
37
|
+
lastSeenAt?: string;
|
|
38
|
+
}, now?: Date): {
|
|
39
|
+
eligible: boolean;
|
|
40
|
+
stale: boolean;
|
|
41
|
+
label: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Whether the reported features / enforcement level / runner flag are still
|
|
45
|
+
* trusted by the scheduler: a VPN-capable agent version with a recent heartbeat.
|
|
46
|
+
* When false they are the Mac's LAST report and may be out of date (it is
|
|
47
|
+
* offline, or too old to be leased VPN builds at all).
|
|
48
|
+
*/
|
|
49
|
+
export declare function agentVpnCapabilitiesCurrent(a: {
|
|
50
|
+
agentVersion: string | null;
|
|
51
|
+
lastSeenAt: string;
|
|
52
|
+
}, now?: Date): boolean;
|
|
53
|
+
/** The co-hosted GitHub Actions runner flag in words: 1 yes, 0 no, anything else unreported. */
|
|
54
|
+
export declare function describeCoHostedRunner(coHostedRunner: number | null | undefined): "runner co-hosted" | "no runner" | "runner unreported";
|
|
55
|
+
/** Replace C0/C1 control characters (ESC, CSI, newline...) so tenant-chosen text cannot drive a terminal. */
|
|
56
|
+
export declare function stripControlChars(text: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* An org as a person reads it: its name when known (with the id for disambiguation), else the id.
|
|
59
|
+
* The name is tenant-controlled (an org owner sets it), so control characters are neutralised.
|
|
60
|
+
*/
|
|
61
|
+
export declare function orgDisplay(orgId: string, orgName: string | null | undefined): string;
|
|
62
|
+
/** One pin as a short phrase: "Acme (org_1) [dedicated]". */
|
|
63
|
+
export declare function describeVpnPin(pin: AgentVpnPin): string;
|
|
64
|
+
//# sourceMappingURL=fleet-vpn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.d.ts","sourceRoot":"","sources":["../src/fleet-vpn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AAEH,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS/D,CAAC;AAEF,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,yCAAyC;AACzC,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAI/E,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EACnG,GAAG,GAAE,IAAiB,GACrB;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAStD;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEnI;AAED,gGAAgG;AAChG,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,GAAG,WAAW,GAAG,mBAAmB,CAIxI;AAED,6GAA6G;AAC7G,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEpF;AAED,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { agentProvesVpnEnforcement, parseEnforcementLevel } from "./agent-readiness.js";
|
|
2
|
+
/**
|
|
3
|
+
* How the fleet shows an agent's VPN facts, shared by the dashboard's Fleet page
|
|
4
|
+
* and `appforge-admin agents list` so the two surfaces never disagree about what
|
|
5
|
+
* a feature id means or what "eligible" is. Pure functions and constants only.
|
|
6
|
+
*
|
|
7
|
+
* Honesty rule: every sentence here describes what the CONTROL PLANE knows, i.e.
|
|
8
|
+
* what the Mac reported at its last heartbeat. A feature chip is a claim the
|
|
9
|
+
* Mac made, not a proof; the scheduler only trusts it while the heartbeat is
|
|
10
|
+
* fresh (`agentVpnCapabilitiesCurrent`), and the page says so.
|
|
11
|
+
*/
|
|
12
|
+
/** What each VPN feature id means, as a one-sentence legend. */
|
|
13
|
+
export const VPN_FEATURE_LEGEND = {
|
|
14
|
+
"egress-policy-v1": "The verified VPN helper enforces a strict egress allow-list for builds (it refuses everything outside the tunnel's policy). Reported only while a helper of a new enough version is installed and verified.",
|
|
15
|
+
"ssh-v1": "Git and ssh traffic can be routed through the tunnel with pinned host keys (the verified helper has the connect subcommand). Reported only while that helper is installed and verified.",
|
|
16
|
+
"sandbox-v1": "Build processes run under a kernel (Seatbelt) sandbox. Reported only while the on-host sandbox self-test has passed recently; a failed or stale self-test withdraws it.",
|
|
17
|
+
"evidence-v1": "Per-build connection evidence: the verified helper streams per-connection records and the agent reports them after each strict-egress or regulated build, so the control plane can issue a signed attestation. Reported only while a helper with per-build credentials and the accounting pipe is installed and verified.",
|
|
18
|
+
};
|
|
19
|
+
/** Legend sentence for a feature id; a short fallback for an id this build does not know. */
|
|
20
|
+
export function describeVpnFeature(id) {
|
|
21
|
+
return VPN_FEATURE_LEGEND[id] ?? "A feature id this dashboard does not know (reported by the agent; no description).";
|
|
22
|
+
}
|
|
23
|
+
/** What each enforcement level means. */
|
|
24
|
+
export const ENFORCEMENT_LEVEL_LEGEND = {
|
|
25
|
+
none: "No kernel-enforced isolation of build processes.",
|
|
26
|
+
sandbox: "Builds run under a kernel (Seatbelt) sandbox profile whose self-test passed on this Mac. Not a dedicated OS user.",
|
|
27
|
+
sandbox_uid: "The sandbox plus a dedicated unprivileged OS user for builds.",
|
|
28
|
+
};
|
|
29
|
+
export function describeEnforcementLevel(level) {
|
|
30
|
+
return ENFORCEMENT_LEVEL_LEGEND[parseEnforcementLevel(level)];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The VPN eligibility as the scheduler applies it in `/agents/lease-job`: a Mac
|
|
34
|
+
* serves VPN builds only when it reports the verified helper (`vpnCapable`), a
|
|
35
|
+
* platform admin approved it (`vpnApproved`), AND the scheduler currently trusts
|
|
36
|
+
* the helper report (`agentVpnCapabilitiesCurrent`: a VPN-capable agent version
|
|
37
|
+
* with a recent heartbeat). `vpnCapable` is sticky between heartbeats, so an
|
|
38
|
+
* offline or too-old Mac can still carry helper=1 + approved=1 yet is never
|
|
39
|
+
* leased a VPN build; `stale` is true for exactly that case.
|
|
40
|
+
*
|
|
41
|
+
* `agentVersion` and `lastSeenAt` are optional only for callers that cannot
|
|
42
|
+
* supply them (an older API omits them): when either is absent freshness is
|
|
43
|
+
* unknown and only the helper and approval halves are judged.
|
|
44
|
+
*/
|
|
45
|
+
export function vpnEligibility(a, now = new Date()) {
|
|
46
|
+
const helper = a.vpnCapable === 1;
|
|
47
|
+
const approved = a.vpnApproved === 1;
|
|
48
|
+
if (helper && approved) {
|
|
49
|
+
const stale = a.agentVersion !== undefined && a.lastSeenAt !== undefined && !agentVpnCapabilitiesCurrent({ agentVersion: a.agentVersion, lastSeenAt: a.lastSeenAt }, now);
|
|
50
|
+
if (stale)
|
|
51
|
+
return { eligible: false, stale: true, label: "not eligible now, helper report stale (offline or old agent)" };
|
|
52
|
+
return { eligible: true, stale: false, label: "eligible" };
|
|
53
|
+
}
|
|
54
|
+
return { eligible: false, stale: false, label: `${helper ? "helper ready" : "no helper"}, ${approved ? "approved" : "not approved"}` };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether the reported features / enforcement level / runner flag are still
|
|
58
|
+
* trusted by the scheduler: a VPN-capable agent version with a recent heartbeat.
|
|
59
|
+
* When false they are the Mac's LAST report and may be out of date (it is
|
|
60
|
+
* offline, or too old to be leased VPN builds at all).
|
|
61
|
+
*/
|
|
62
|
+
export function agentVpnCapabilitiesCurrent(a, now = new Date()) {
|
|
63
|
+
return agentProvesVpnEnforcement(a, now);
|
|
64
|
+
}
|
|
65
|
+
/** The co-hosted GitHub Actions runner flag in words: 1 yes, 0 no, anything else unreported. */
|
|
66
|
+
export function describeCoHostedRunner(coHostedRunner) {
|
|
67
|
+
if (coHostedRunner === 1)
|
|
68
|
+
return "runner co-hosted";
|
|
69
|
+
if (coHostedRunner === 0)
|
|
70
|
+
return "no runner";
|
|
71
|
+
return "runner unreported";
|
|
72
|
+
}
|
|
73
|
+
/** Replace C0/C1 control characters (ESC, CSI, newline...) so tenant-chosen text cannot drive a terminal. */
|
|
74
|
+
export function stripControlChars(text) {
|
|
75
|
+
return text.replace(/[\x00-\x1f\x7f-\x9f]/g, "\uFFFD");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* An org as a person reads it: its name when known (with the id for disambiguation), else the id.
|
|
79
|
+
* The name is tenant-controlled (an org owner sets it), so control characters are neutralised.
|
|
80
|
+
*/
|
|
81
|
+
export function orgDisplay(orgId, orgName) {
|
|
82
|
+
return orgName ? `${stripControlChars(orgName)} (${orgId})` : orgId;
|
|
83
|
+
}
|
|
84
|
+
/** One pin as a short phrase: "Acme (org_1) [dedicated]". */
|
|
85
|
+
export function describeVpnPin(pin) {
|
|
86
|
+
return `${orgDisplay(pin.orgId, pin.orgName)} [${pin.hostPolicy}]`;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=fleet-vpn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.js","sourceRoot":"","sources":["../src/fleet-vpn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.test.d.ts","sourceRoot":"","sources":["../src/fleet-vpn.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { ENFORCEMENT_LEVELS, KNOWN_VPN_FEATURES, MIN_VPN_AGENT_VERSION, VPN_CAPABILITY_MAX_AGE_SECONDS, VPN_FEATURE_LEGEND, agentVpnCapabilitiesCurrent, describeCoHostedRunner, describeEnforcementLevel, describeVpnFeature, describeVpnPin, orgDisplay, vpnEligibility, } from "./index.js";
|
|
4
|
+
test("every feature id the program introduces has a legend sentence, and an unknown id gets an honest fallback", () => {
|
|
5
|
+
for (const id of KNOWN_VPN_FEATURES) {
|
|
6
|
+
assert.ok(VPN_FEATURE_LEGEND[id] && VPN_FEATURE_LEGEND[id].length > 20, `no legend for ${id}`);
|
|
7
|
+
assert.equal(describeVpnFeature(id), VPN_FEATURE_LEGEND[id]);
|
|
8
|
+
}
|
|
9
|
+
assert.match(describeVpnFeature("future-thing-v9"), /does not know/);
|
|
10
|
+
});
|
|
11
|
+
test("the legend does not claim compliance, certification or attestation properties the code does not give", () => {
|
|
12
|
+
const all = [...Object.values(VPN_FEATURE_LEGEND), ...ENFORCEMENT_LEVELS.map(describeEnforcementLevel)].join("\n");
|
|
13
|
+
assert.ok(!/SOC ?2|FIPS|complian|certif|bank-grade|secure|guarantee/i.test(all), all);
|
|
14
|
+
});
|
|
15
|
+
test("every enforcement level has a description; an unrecognised level reads as none", () => {
|
|
16
|
+
for (const level of ENFORCEMENT_LEVELS)
|
|
17
|
+
assert.ok(describeEnforcementLevel(level).length > 10);
|
|
18
|
+
assert.equal(describeEnforcementLevel("root"), describeEnforcementLevel("none"));
|
|
19
|
+
assert.equal(describeEnforcementLevel(undefined), describeEnforcementLevel("none"));
|
|
20
|
+
});
|
|
21
|
+
test("vpnEligibility is eligible only with BOTH the helper and the admin approval, and spells out the missing half otherwise", () => {
|
|
22
|
+
assert.deepEqual(vpnEligibility({ vpnCapable: 1, vpnApproved: 1 }), { eligible: true, stale: false, label: "eligible" });
|
|
23
|
+
assert.deepEqual(vpnEligibility({ vpnCapable: 1, vpnApproved: 0 }), { eligible: false, stale: false, label: "helper ready, not approved" });
|
|
24
|
+
assert.deepEqual(vpnEligibility({ vpnCapable: 0, vpnApproved: 1 }), { eligible: false, stale: false, label: "no helper, approved" });
|
|
25
|
+
assert.deepEqual(vpnEligibility({}), { eligible: false, stale: false, label: "no helper, not approved" });
|
|
26
|
+
});
|
|
27
|
+
test("vpnEligibility is not eligible (stale) for an approved helper Mac that is offline or too old, as the scheduler refuses it", () => {
|
|
28
|
+
const now = new Date("2026-10-03T12:00:00Z");
|
|
29
|
+
const seen = (secondsAgo) => new Date(now.getTime() - secondsAgo * 1000).toISOString();
|
|
30
|
+
const base = { vpnCapable: 1, vpnApproved: 1 };
|
|
31
|
+
assert.equal(vpnEligibility({ ...base, agentVersion: "0.3.0", lastSeenAt: seen(5) }, now).eligible, true);
|
|
32
|
+
for (const r of [
|
|
33
|
+
vpnEligibility({ ...base, agentVersion: "0.3.0", lastSeenAt: seen(VPN_CAPABILITY_MAX_AGE_SECONDS + 5) }, now),
|
|
34
|
+
vpnEligibility({ ...base, agentVersion: "0.1.9", lastSeenAt: seen(5) }, now),
|
|
35
|
+
vpnEligibility({ ...base, agentVersion: null, lastSeenAt: seen(5) }, now),
|
|
36
|
+
]) {
|
|
37
|
+
assert.equal(r.eligible, false);
|
|
38
|
+
assert.equal(r.stale, true);
|
|
39
|
+
assert.match(r.label, /stale/);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
test("agentVpnCapabilitiesCurrent follows the scheduler's proof: a VPN-capable version and a recent heartbeat", () => {
|
|
43
|
+
const now = new Date("2026-10-03T12:00:00Z");
|
|
44
|
+
const seen = (secondsAgo) => new Date(now.getTime() - secondsAgo * 1000).toISOString();
|
|
45
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: "0.3.0", lastSeenAt: seen(5) }, now), true);
|
|
46
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: "0.3.0", lastSeenAt: seen(VPN_CAPABILITY_MAX_AGE_SECONDS + 5) }, now), false);
|
|
47
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: null, lastSeenAt: seen(5) }, now), false);
|
|
48
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: "0.1.9", lastSeenAt: seen(5) }, now), false);
|
|
49
|
+
assert.equal(agentVpnCapabilitiesCurrent({ agentVersion: MIN_VPN_AGENT_VERSION, lastSeenAt: seen(5) }, now), true);
|
|
50
|
+
});
|
|
51
|
+
test("the co-hosted runner flag reads yes / no / unreported, and null or undefined are never 'no runner'", () => {
|
|
52
|
+
assert.equal(describeCoHostedRunner(1), "runner co-hosted");
|
|
53
|
+
assert.equal(describeCoHostedRunner(0), "no runner");
|
|
54
|
+
assert.equal(describeCoHostedRunner(null), "runner unreported");
|
|
55
|
+
assert.equal(describeCoHostedRunner(undefined), "runner unreported");
|
|
56
|
+
});
|
|
57
|
+
test("orgDisplay neutralises control characters in a tenant-chosen org name", () => {
|
|
58
|
+
const shown = orgDisplay("org_1", "Evil\u001b[2K\u001b]0;pwned\u0007\nName");
|
|
59
|
+
assert.ok(!/[\x00-\x1f\x7f-\x9f]/.test(shown), JSON.stringify(shown));
|
|
60
|
+
assert.match(shown, /^Evil.*Name \(org_1\)$/);
|
|
61
|
+
assert.ok(!/[\x00-\x1f\x7f-\x9f]/.test(describeVpnPin({ orgId: "o", orgName: "a\u001bb", hostPolicy: "pinned" })));
|
|
62
|
+
});
|
|
63
|
+
test("orgDisplay and describeVpnPin show the name with its id, or the bare id when the name does not resolve", () => {
|
|
64
|
+
assert.equal(orgDisplay("org_1", "Acme"), "Acme (org_1)");
|
|
65
|
+
assert.equal(orgDisplay("org_1", null), "org_1");
|
|
66
|
+
assert.equal(describeVpnPin({ orgId: "org_1", orgName: "Acme", hostPolicy: "dedicated" }), "Acme (org_1) [dedicated]");
|
|
67
|
+
assert.equal(describeVpnPin({ orgId: "org_1", orgName: null, hostPolicy: "pinned" }), "org_1 [pinned]");
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=fleet-vpn.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-vpn.test.js","sourceRoot":"","sources":["../src/fleet-vpn.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,14 +2,20 @@ export * from "./types.js";
|
|
|
2
2
|
export * from "./schemas.js";
|
|
3
3
|
export * from "./api-client.js";
|
|
4
4
|
export * from "./agent-readiness.js";
|
|
5
|
+
export * from "./fleet-vpn.js";
|
|
5
6
|
export * from "./ota-rollout.js";
|
|
6
7
|
export * from "./ota-bundle-hash.js";
|
|
7
8
|
export * from "./ota-artifact.js";
|
|
8
9
|
export * from "./vpn-network.js";
|
|
9
10
|
export * from "./vpn-egress.js";
|
|
10
11
|
export * from "./vpn-ssh.js";
|
|
12
|
+
export * from "./vpn-sandbox.js";
|
|
13
|
+
export * from "./ssh-deploy-key.js";
|
|
11
14
|
export * from "./vpn-egress-presets.js";
|
|
12
15
|
export * from "./artifact-upload.js";
|
|
13
16
|
export * from "./build-vpn.js";
|
|
14
17
|
export * from "./attestation.js";
|
|
18
|
+
export * from "./attestation-signing.js";
|
|
19
|
+
export * from "./attestation-keys.js";
|
|
20
|
+
export * from "./vpn-attestation.js";
|
|
15
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,14 +2,20 @@ export * from "./types.js";
|
|
|
2
2
|
export * from "./schemas.js";
|
|
3
3
|
export * from "./api-client.js";
|
|
4
4
|
export * from "./agent-readiness.js";
|
|
5
|
+
export * from "./fleet-vpn.js";
|
|
5
6
|
export * from "./ota-rollout.js";
|
|
6
7
|
export * from "./ota-bundle-hash.js";
|
|
7
8
|
export * from "./ota-artifact.js";
|
|
8
9
|
export * from "./vpn-network.js";
|
|
9
10
|
export * from "./vpn-egress.js";
|
|
10
11
|
export * from "./vpn-ssh.js";
|
|
12
|
+
export * from "./vpn-sandbox.js";
|
|
13
|
+
export * from "./ssh-deploy-key.js";
|
|
11
14
|
export * from "./vpn-egress-presets.js";
|
|
12
15
|
export * from "./artifact-upload.js";
|
|
13
16
|
export * from "./build-vpn.js";
|
|
14
17
|
export * from "./attestation.js";
|
|
18
|
+
export * from "./attestation-signing.js";
|
|
19
|
+
export * from "./attestation-keys.js";
|
|
20
|
+
export * from "./vpn-attestation.js";
|
|
15
21
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas-repo-url.test.d.ts","sourceRoot":"","sources":["../src/schemas-repo-url.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createAppSchema, setVpnSshHostsSchema, updateAppSchema } from "./schemas.js";
|
|
4
|
+
const create = (repoUrl) => createAppSchema.safeParse({ name: "a", platform: "flutter", repoUrl });
|
|
5
|
+
const update = (repoUrl) => updateAppSchema.safeParse({ repoUrl });
|
|
6
|
+
// The transport matrix: https as always; ssh syntactically valid (the routes decide whether THIS app may use it).
|
|
7
|
+
const ACCEPTED = [
|
|
8
|
+
"https://github.com/org/repo",
|
|
9
|
+
"https://github.com/org/repo.git",
|
|
10
|
+
"https://git.corp.test:8443/org/repo.git",
|
|
11
|
+
"ssh://git@git.corp.test/org/repo.git",
|
|
12
|
+
"ssh://git@10.20.0.5:2222/srv/git/repo.git",
|
|
13
|
+
"git@git.corp.test:org/repo.git",
|
|
14
|
+
"git@git.corp.test:/srv/git/repo.git",
|
|
15
|
+
];
|
|
16
|
+
const REJECTED = [
|
|
17
|
+
"http://github.com/org/repo",
|
|
18
|
+
"git://github.com/org/repo",
|
|
19
|
+
"file:///etc/passwd",
|
|
20
|
+
"/local/path",
|
|
21
|
+
"ext::sh -c id",
|
|
22
|
+
"ssh://git.corp.test/org/repo.git", // no user
|
|
23
|
+
"ssh://git@-oProxyCommand=id/x",
|
|
24
|
+
"ssh://git@git.corp.test/org/repo.git;id",
|
|
25
|
+
"git@git.corp.test:org/$(id)",
|
|
26
|
+
"git@git.corp.test:../x",
|
|
27
|
+
"git.corp.test:org/repo.git",
|
|
28
|
+
"git+ssh://git@git.corp.test/org/repo.git",
|
|
29
|
+
"not a url",
|
|
30
|
+
"",
|
|
31
|
+
];
|
|
32
|
+
test("repoUrl: https and strictly-formed ssh remotes are accepted by create and update", () => {
|
|
33
|
+
for (const u of ACCEPTED) {
|
|
34
|
+
assert.equal(create(u).success, true, `create ${u}`);
|
|
35
|
+
assert.equal(update(u).success, true, `update ${u}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
test("repoUrl: every other transport and every malformed ssh remote is refused, with a message", () => {
|
|
39
|
+
for (const u of REJECTED) {
|
|
40
|
+
const c = create(u);
|
|
41
|
+
assert.equal(c.success, false, `create ${u}`);
|
|
42
|
+
assert.equal(update(u).success, false, `update ${u}`);
|
|
43
|
+
if (!c.success)
|
|
44
|
+
assert.ok(c.error.issues[0].message.length > 0);
|
|
45
|
+
}
|
|
46
|
+
assert.equal(create(42).success, false);
|
|
47
|
+
assert.equal(create(undefined).success, false);
|
|
48
|
+
});
|
|
49
|
+
test("repoUrl: the https-only wording is kept for an unsupported scheme and mentions the ssh exception", () => {
|
|
50
|
+
const r = create("git://github.com/org/repo");
|
|
51
|
+
assert.equal(r.success, false);
|
|
52
|
+
if (!r.success)
|
|
53
|
+
assert.match(r.error.issues[0].message, /https:\/\/ URL, or an ssh remote/);
|
|
54
|
+
});
|
|
55
|
+
test("createAppSchema takes an optional useVpn", () => {
|
|
56
|
+
assert.equal(createAppSchema.safeParse({ name: "a", platform: "ios", repoUrl: "https://github.com/o/r", useVpn: true }).success, true);
|
|
57
|
+
assert.equal(createAppSchema.safeParse({ name: "a", platform: "ios", repoUrl: "https://github.com/o/r", useVpn: "yes" }).success, false);
|
|
58
|
+
});
|
|
59
|
+
test("setVpnSshHostsSchema bounds the shape only", () => {
|
|
60
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: [] }).success, true);
|
|
61
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: ["a b c"] }).success, true);
|
|
62
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: "text" }).success, false);
|
|
63
|
+
assert.equal(setVpnSshHostsSchema.safeParse({}).success, false);
|
|
64
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: Array(513).fill("x") }).success, false);
|
|
65
|
+
assert.equal(setVpnSshHostsSchema.safeParse({ knownHosts: ["x".repeat(5000)] }).success, false);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=schemas-repo-url.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas-repo-url.test.js","sourceRoot":"","sources":["../src/schemas-repo-url.test.ts"],"names":[],"mappings":"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"}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -471,15 +471,20 @@ export declare const createAppSchema: z.ZodObject<{
|
|
|
471
471
|
platform: z.ZodEnum<["ios", "android", "flutter", "react-native"]>;
|
|
472
472
|
repoUrl: z.ZodEffects<z.ZodString, string, string>;
|
|
473
473
|
defaultBranch: z.ZodDefault<z.ZodString>;
|
|
474
|
+
/** Build through the org's site-to-site VPN tunnel from the start. Needed to create an app with an ssh
|
|
475
|
+
* `repoUrl` (the route requires it, and an owner/admin); the same rules as `PATCH /apps/:appId`'s `useVpn`. */
|
|
476
|
+
useVpn: z.ZodOptional<z.ZodBoolean>;
|
|
474
477
|
}, "strip", z.ZodTypeAny, {
|
|
475
478
|
name: string;
|
|
476
479
|
platform: "ios" | "android" | "flutter" | "react-native";
|
|
477
480
|
repoUrl: string;
|
|
478
481
|
defaultBranch: string;
|
|
482
|
+
useVpn?: boolean | undefined;
|
|
479
483
|
}, {
|
|
480
484
|
name: string;
|
|
481
485
|
platform: "ios" | "android" | "flutter" | "react-native";
|
|
482
486
|
repoUrl: string;
|
|
487
|
+
useVpn?: boolean | undefined;
|
|
483
488
|
defaultBranch?: string | undefined;
|
|
484
489
|
}>;
|
|
485
490
|
export declare const updateAppSchema: z.ZodObject<{
|
|
@@ -1233,4 +1238,17 @@ export declare const reportVpnEventSchema: z.ZodObject<{
|
|
|
1233
1238
|
event: "tunnel_up" | "tunnel_down" | "handshake_ok" | "handshake_failed";
|
|
1234
1239
|
detail?: string | undefined;
|
|
1235
1240
|
}>;
|
|
1241
|
+
/**
|
|
1242
|
+
* `PUT /orgs/:orgId/vpn/ssh-hosts` (regulated-VPN program, PR-G): replaces the
|
|
1243
|
+
* tunnel's pinned SSH host keys. Only the SHAPE is checked here (bounded array of
|
|
1244
|
+
* bounded strings); the content rules, with their per-line error messages, are
|
|
1245
|
+
* `validateKnownHostsInput` in vpn-ssh.ts, run by the route. `[]` clears them.
|
|
1246
|
+
*/
|
|
1247
|
+
export declare const setVpnSshHostsSchema: z.ZodObject<{
|
|
1248
|
+
knownHosts: z.ZodArray<z.ZodString, "many">;
|
|
1249
|
+
}, "strip", z.ZodTypeAny, {
|
|
1250
|
+
knownHosts: string[];
|
|
1251
|
+
}, {
|
|
1252
|
+
knownHosts: string[];
|
|
1253
|
+
}>;
|
|
1236
1254
|
//# sourceMappingURL=schemas.d.ts.map
|
package/dist/schemas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,cAAc,0DAAwD,CAAC;AAEpF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAwBjC;;;sEAGkE;;IAElE,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAO7E,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAf/B;;;0EAGkE;;QAElE,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAe7E,CAAC;AAgCH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;EAKvB,CAAC;AAEH,eAAO,MAAM,WAAW;;;IAGtB;;;uEAGmE;;IAEnE;;yCAEqC;;;;;;;;;;;;EAErC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAC;AAEH;;;;;;;;;;;;uEAYuE;AACvE,eAAO,MAAM,iBAAiB;;;;;;;;;EAG5B,CAAC;AAEH;;;4CAG4C;AAC5C,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAC;AAEH;;;;;;;;;;;;;;;4CAe4C;AAC5C,eAAO,MAAM,6BAA6B;;;;;;EAExC,CAAC;AAEH;;;;;wDAKwD;AACxD,eAAO,MAAM,oBAAoB;;;;;;;;;EAG/B,CAAC;AAEH;;;;;gEAKgE;AAChE,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAC;AAEH;;;4BAG4B;AAC5B,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAC;AAuEH,eAAO,MAAM,eAAe;;;;;IAK1B;oHACgH;;;;;;;;;;;;;;EAEhH,CAAC;AAKH,eAAO,MAAM,eAAe;;;;IAI1B;;iEAE6D;;;;;;;;;;;;EAE7D,CAAC;AAQH,eAAO,MAAM,eAAe;;;;;;;;;EAU1B,CAAC;AA0EH,eAAO,MAAM,kBAAkB;;;;;;;;;EAiB7B,CAAC;AAiBH,eAAO,MAAM,kBAAkB,0EAA2E,CAAC;AAE3G,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAQlE,eAAO,MAAM,oBAAoB;;;;;;;;;EAe/B,CAAC;AAeH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;EAS7B,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;EAEtB,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyB5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;IA4B/B,iDAAiD;;IAEjD;;2EAEuE;;IAEvE;;;;wCAIoC;;IAEpC;;;;qEAIiE;;IAEjE;6DACyD;;IAEzD;;;uEAGmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEnE,CAAC;AAgBH,eAAO,MAAM,eAAe;;;;;;;;;EAG1B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B9B,CAAC;AASH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;EAKlC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;EAEpC,CAAC;AAOH,eAAO,MAAM,6BAA6B;;;;;;EAExC,CAAC;AAEH;;;;;oBAKoB;AACpB,eAAO,MAAM,2BAA2B;;;;;;EAEtC,CAAC;AAEH;;;;2EAI2E;AAC3E,eAAO,MAAM,yBAAyB;;;;;;EAEpC,CAAC;AAEH;;;;qDAIqD;AACrD,eAAO,MAAM,0BAA0B;;;;;;EAErC,CAAC;AAEH;;;uEAGuE;AACvE,eAAO,MAAM,sBAAsB;;;;;;EAEjC,CAAC;AAEH;;;;;oEAKoE;AACpE,eAAO,MAAM,kCAAkC;;;;;;EAE7C,CAAC;AAEH;;;;;;oDAMoD;AACpD,eAAO,MAAM,qBAAqB;;;;;;EAEhC,CAAC;AAgDH;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB;IAnCpC,uFAAuF;;;IAGvF,qCAAqC;;IAErC,qGAAqG;;;;;;;;;;;;;;;;;IAqCrG;+DAC2D;;IAE3D;2EACuE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGvE,CAAC;AAEH,eAAO,MAAM,yBAAyB;IAnDpC,uFAAuF;;;IAGvF,qCAAqC;;IAErC,qGAAqG;;;;;;;;;;;;;;;IAmDrG,8DAA8D;;;;IAI9D,4EAA4E;;IAE5E;;8CAE0C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG1C,CAAC;AAEH,uHAAuH;AACvH,eAAO,MAAM,qBAAqB;IAtEhC,uFAAuF;;;IAGvF,qCAAqC;;IAErC,qGAAqG;;;;;;;;;;;;;;;;;IAqCrG;+DAC2D;;IAE3D;2EACuE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwB0B,CAAC;AAEpG,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB;IAzEhC,uFAAuF;;;IAGvF,qCAAqC;;IAErC,qGAAqG;;;;;;;;;;;;;;;IAmDrG,8DAA8D;;;;IAI9D,4EAA4E;;IAE5E;;8CAE0C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASuD,CAAC;AAEpG;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;EAShC,CAAC;AAEH;;;;gEAIgE;AAChE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAGH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAC"}
|
package/dist/schemas.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ENFORCEMENT_LEVELS, MAX_VPN_FEATURES, VPN_FEATURE_ID_PATTERN, VPN_HOST_POLICIES } from "./agent-readiness.js";
|
|
3
|
+
import { looksLikeSshGitRemote, MAX_KNOWN_HOSTS_LINE_LENGTH, parseGitRemote } from "./vpn-ssh.js";
|
|
3
4
|
import { VPN_COMPLIANCE_MODES, VPN_EGRESS_MAX_CUSTOM_RULES, VPN_EGRESS_MAX_PRESETS, validateEgressSettings } from "./vpn-egress-presets.js";
|
|
4
5
|
export const platformSchema = z.enum(["ios", "android", "flutter", "react-native"]);
|
|
5
6
|
export const appforgeWorkflowSchema = z.object({
|
|
@@ -190,22 +191,58 @@ export const resetPasswordSchema = z.object({
|
|
|
190
191
|
// transport ever reaches a real `git clone` on shared, multi-tenant
|
|
191
192
|
// infrastructure (`agents.ts`'s own docblock: "the fleet is a shared
|
|
192
193
|
// pool... not one Mac mini per customer"). This product only ever
|
|
193
|
-
// integrates public GitHub repos over HTTPS
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
// at the point of the actual clone, in case a repoUrl ever reaches the
|
|
194
|
+
// integrates public GitHub repos over HTTPS, and a private git server only
|
|
195
|
+
// through a customer's site-to-site VPN tunnel (see below), so the set of
|
|
196
|
+
// transports is exactly those two. See `packages/agent/src/lib/builder.ts`'s own
|
|
197
|
+
// `runScript` git-clone call for the matching defense-in-depth restriction
|
|
198
|
+
// applied at the point of the actual clone, in case a repoUrl ever reaches the
|
|
199
199
|
// agent by some path other than this schema.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
//
|
|
201
|
+
// Regulated-VPN program, PR-G: an `ssh://user@host[:port]/path` or scp-like
|
|
202
|
+
// `user@host:path` remote is now SYNTACTICALLY accepted (strict grammar, see
|
|
203
|
+
// `parseGitRemote` in vpn-ssh.ts: a user is required, the host is a DNS name or
|
|
204
|
+
// IPv4 address, nothing an ssh client could read as an option or a command).
|
|
205
|
+
// That is only the shape check. Whether THIS app may use one is a cross-resource
|
|
206
|
+
// rule this schema cannot know and the routes enforce (`POST /orgs/:orgId/apps`,
|
|
207
|
+
// `PATCH /apps/:appId`): the app must use the org's VPN tunnel, the tunnel must
|
|
208
|
+
// not be disabled and must have pinned SSH host keys, and the change needs an org
|
|
209
|
+
// owner/admin. The lease re-checks all of it, and the agent clones an ssh remote
|
|
210
|
+
// only through the build's pinned-host-key ssh config (never directly, never over
|
|
211
|
+
// https instead). `file:`, `git:`, `ext::`, plain `http:` and every other scheme
|
|
212
|
+
// stay refused.
|
|
213
|
+
const repoUrlSchema = z.string().superRefine((v, ctx) => {
|
|
214
|
+
if (looksLikeSshGitRemote(v)) {
|
|
215
|
+
if (parseGitRemote(v)?.kind !== "ssh") {
|
|
216
|
+
ctx.addIssue({
|
|
217
|
+
code: z.ZodIssueCode.custom,
|
|
218
|
+
message: "not a valid ssh remote: use ssh://user@host[:port]/path or user@host:path (a user name is required; the host is a DNS name or IPv4 address)",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
let protocol = null;
|
|
224
|
+
try {
|
|
225
|
+
protocol = new URL(v).protocol;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
ctx.addIssue({ code: z.ZodIssueCode.invalid_string, validation: "url", message: "Invalid url" });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (protocol !== "https:") {
|
|
232
|
+
ctx.addIssue({
|
|
233
|
+
code: z.ZodIssueCode.custom,
|
|
234
|
+
message: "must be an https:// URL, or an ssh remote for an app that builds through a VPN tunnel with SSH host keys — no other transport (file:, ext:, git:, http:, ...) is supported",
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
204
238
|
export const createAppSchema = z.object({
|
|
205
239
|
name: z.string().min(1).max(255, "must be at most 255 characters"),
|
|
206
240
|
platform: platformSchema,
|
|
207
241
|
repoUrl: repoUrlSchema,
|
|
208
242
|
defaultBranch: z.string().min(1).default("main"),
|
|
243
|
+
/** Build through the org's site-to-site VPN tunnel from the start. Needed to create an app with an ssh
|
|
244
|
+
* `repoUrl` (the route requires it, and an owner/admin); the same rules as `PATCH /apps/:appId`'s `useVpn`. */
|
|
245
|
+
useVpn: z.boolean().optional(),
|
|
209
246
|
});
|
|
210
247
|
// `platform` is deliberately excluded — see updateApp's own docblock in
|
|
211
248
|
// packages/db/src/apps.ts. Every field optional; the route only applies
|
|
@@ -699,4 +736,13 @@ export const reportVpnEventSchema = z.object({
|
|
|
699
736
|
event: z.enum(["tunnel_up", "tunnel_down", "handshake_ok", "handshake_failed"]),
|
|
700
737
|
detail: z.string().max(300).optional(),
|
|
701
738
|
});
|
|
739
|
+
/**
|
|
740
|
+
* `PUT /orgs/:orgId/vpn/ssh-hosts` (regulated-VPN program, PR-G): replaces the
|
|
741
|
+
* tunnel's pinned SSH host keys. Only the SHAPE is checked here (bounded array of
|
|
742
|
+
* bounded strings); the content rules, with their per-line error messages, are
|
|
743
|
+
* `validateKnownHostsInput` in vpn-ssh.ts, run by the route. `[]` clears them.
|
|
744
|
+
*/
|
|
745
|
+
export const setVpnSshHostsSchema = z.object({
|
|
746
|
+
knownHosts: z.array(z.string().max(MAX_KNOWN_HOSTS_LINE_LENGTH * 2)).max(512),
|
|
747
|
+
});
|
|
702
748
|
//# sourceMappingURL=schemas.js.map
|