@appforge-ci/core 0.3.0 → 0.3.2

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.
Files changed (105) hide show
  1. package/dist/agent-readiness.d.ts +11 -3
  2. package/dist/agent-readiness.d.ts.map +1 -1
  3. package/dist/agent-readiness.js +15 -5
  4. package/dist/agent-readiness.js.map +1 -1
  5. package/dist/agent-readiness.test.js +34 -10
  6. package/dist/agent-readiness.test.js.map +1 -1
  7. package/dist/api-client.d.ts +39 -1
  8. package/dist/api-client.d.ts.map +1 -1
  9. package/dist/api-client.js +52 -0
  10. package/dist/api-client.js.map +1 -1
  11. package/dist/appforge-verify.test.d.ts +2 -0
  12. package/dist/appforge-verify.test.d.ts.map +1 -0
  13. package/dist/appforge-verify.test.js +958 -0
  14. package/dist/appforge-verify.test.js.map +1 -0
  15. package/dist/attestation-keys.d.ts +20 -0
  16. package/dist/attestation-keys.d.ts.map +1 -0
  17. package/dist/attestation-keys.js +27 -0
  18. package/dist/attestation-keys.js.map +1 -0
  19. package/dist/attestation-keys.test.d.ts +2 -0
  20. package/dist/attestation-keys.test.d.ts.map +1 -0
  21. package/dist/attestation-keys.test.js +45 -0
  22. package/dist/attestation-keys.test.js.map +1 -0
  23. package/dist/attestation-signing.d.ts +226 -0
  24. package/dist/attestation-signing.d.ts.map +1 -0
  25. package/dist/attestation-signing.js +276 -0
  26. package/dist/attestation-signing.js.map +1 -0
  27. package/dist/attestation-signing.test.d.ts +2 -0
  28. package/dist/attestation-signing.test.d.ts.map +1 -0
  29. package/dist/attestation-signing.test.js +250 -0
  30. package/dist/attestation-signing.test.js.map +1 -0
  31. package/dist/attestation-workerd.test.d.ts +2 -0
  32. package/dist/attestation-workerd.test.d.ts.map +1 -0
  33. package/dist/attestation-workerd.test.js +183 -0
  34. package/dist/attestation-workerd.test.js.map +1 -0
  35. package/dist/attestation.d.ts +169 -22
  36. package/dist/attestation.d.ts.map +1 -1
  37. package/dist/attestation.js +42 -16
  38. package/dist/attestation.js.map +1 -1
  39. package/dist/build-vpn.d.ts +11 -1
  40. package/dist/build-vpn.d.ts.map +1 -1
  41. package/dist/build-vpn.js +27 -0
  42. package/dist/build-vpn.js.map +1 -1
  43. package/dist/fleet-vpn.d.ts +64 -0
  44. package/dist/fleet-vpn.d.ts.map +1 -0
  45. package/dist/fleet-vpn.js +88 -0
  46. package/dist/fleet-vpn.js.map +1 -0
  47. package/dist/fleet-vpn.test.d.ts +2 -0
  48. package/dist/fleet-vpn.test.d.ts.map +1 -0
  49. package/dist/fleet-vpn.test.js +69 -0
  50. package/dist/fleet-vpn.test.js.map +1 -0
  51. package/dist/index.d.ts +6 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +6 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/schemas-repo-url.test.d.ts +2 -0
  56. package/dist/schemas-repo-url.test.d.ts.map +1 -0
  57. package/dist/schemas-repo-url.test.js +67 -0
  58. package/dist/schemas-repo-url.test.js.map +1 -0
  59. package/dist/schemas.d.ts +18 -0
  60. package/dist/schemas.d.ts.map +1 -1
  61. package/dist/schemas.js +56 -10
  62. package/dist/schemas.js.map +1 -1
  63. package/dist/ssh-deploy-key.d.ts +59 -0
  64. package/dist/ssh-deploy-key.d.ts.map +1 -0
  65. package/dist/ssh-deploy-key.js +124 -0
  66. package/dist/ssh-deploy-key.js.map +1 -0
  67. package/dist/ssh-deploy-key.test.d.ts +2 -0
  68. package/dist/ssh-deploy-key.test.d.ts.map +1 -0
  69. package/dist/ssh-deploy-key.test.js +117 -0
  70. package/dist/ssh-deploy-key.test.js.map +1 -0
  71. package/dist/types.d.ts +57 -2
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/types.js +0 -5
  74. package/dist/types.js.map +1 -1
  75. package/dist/vpn-attestation-workerd.test.d.ts +2 -0
  76. package/dist/vpn-attestation-workerd.test.d.ts.map +1 -0
  77. package/dist/vpn-attestation-workerd.test.js +184 -0
  78. package/dist/vpn-attestation-workerd.test.js.map +1 -0
  79. package/dist/vpn-attestation.d.ts +1015 -0
  80. package/dist/vpn-attestation.d.ts.map +1 -0
  81. package/dist/vpn-attestation.js +963 -0
  82. package/dist/vpn-attestation.js.map +1 -0
  83. package/dist/vpn-attestation.test.d.ts +2 -0
  84. package/dist/vpn-attestation.test.d.ts.map +1 -0
  85. package/dist/vpn-attestation.test.js +613 -0
  86. package/dist/vpn-attestation.test.js.map +1 -0
  87. package/dist/vpn-egress-presets.d.ts +1 -1
  88. package/dist/vpn-egress-presets.d.ts.map +1 -1
  89. package/dist/vpn-egress-presets.js +2 -2
  90. package/dist/vpn-egress-presets.js.map +1 -1
  91. package/dist/vpn-sandbox.d.ts +67 -0
  92. package/dist/vpn-sandbox.d.ts.map +1 -0
  93. package/dist/vpn-sandbox.js +22 -0
  94. package/dist/vpn-sandbox.js.map +1 -0
  95. package/dist/vpn-sandbox.test.d.ts +2 -0
  96. package/dist/vpn-sandbox.test.d.ts.map +1 -0
  97. package/dist/vpn-sandbox.test.js +34 -0
  98. package/dist/vpn-sandbox.test.js.map +1 -0
  99. package/dist/vpn-ssh.d.ts +93 -0
  100. package/dist/vpn-ssh.d.ts.map +1 -1
  101. package/dist/vpn-ssh.js +213 -1
  102. package/dist/vpn-ssh.js.map +1 -1
  103. package/dist/vpn-ssh.test.js +200 -1
  104. package/dist/vpn-ssh.test.js.map +1 -1
  105. 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
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fleet-vpn.test.d.ts.map
@@ -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
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schemas-repo-url.test.d.ts.map
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,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;AAuCH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;EAK1B,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"}
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 (no SSH-deploy-key feature
194
- // exists anywhere in this codebase), so restricting to exactly `https:`
195
- // isn't just "safe enough," it's the only scheme any legitimate caller
196
- // ever needs. See `packages/agent/src/lib/builder.ts`'s own `runScript`
197
- // git-clone call for the matching defense-in-depth restriction applied
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
- const repoUrlSchema = z
201
- .string()
202
- .url()
203
- .refine((v) => new URL(v).protocol === "https:", "must be an https:// URL — no other transport (file:, ext:, ssh:, git:, ...) is supported");
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