Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepgatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
ssh-server.test.ts6.1 KB · 211 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**
 * Tests for Block SSH-1 — ssh-server.ts
 *
 * We test the pure helpers in isolation (no DB, no real SSH sockets):
 *   - parseGitCommand   — command string → {service, owner, repo}
 *   - computePushedRefs — before/after show-ref → PushRef[]
 *
 * resolveUserByKeyBlob is DB-dependent; we cover it with a DB-skip guard.
 */

import { describe, expect, test, beforeEach, mock } from "bun:test";
import {
  parseGitCommand,
  computePushedRefs,
  resolveUserByKeyBlob,
} from "../lib/ssh-server";

// ---------------------------------------------------------------------------
// parseGitCommand
// ---------------------------------------------------------------------------

describe("parseGitCommand", () => {
  test("upload-pack with leading slash", () => {
    const r = parseGitCommand("git-upload-pack '/alice/myrepo.git'");
    expect(r).toEqual({
      service: "git-upload-pack",
      owner: "alice",
      repo: "myrepo",
    });
  });

  test("receive-pack with leading slash", () => {
    const r = parseGitCommand("git-receive-pack '/bob/project.git'");
    expect(r).toEqual({
      service: "git-receive-pack",
      owner: "bob",
      repo: "project",
    });
  });

  test("upload-pack without leading slash (some clients omit it)", () => {
    const r = parseGitCommand("git-upload-pack 'alice/myrepo.git'");
    expect(r).toEqual({
      service: "git-upload-pack",
      owner: "alice",
      repo: "myrepo",
    });
  });

  test("upload-pack without quotes (rare but valid)", () => {
    const r = parseGitCommand("git-upload-pack /alice/myrepo.git");
    expect(r).toEqual({
      service: "git-upload-pack",
      owner: "alice",
      repo: "myrepo",
    });
  });

  test(".git suffix is stripped", () => {
    const r = parseGitCommand("git-upload-pack '/alice/myrepo.git'");
    expect(r?.repo).toBe("myrepo");
  });

  test("repo without .git suffix is accepted", () => {
    const r = parseGitCommand("git-upload-pack '/alice/myrepo'");
    expect(r?.repo).toBe("myrepo");
  });

  test("repo with dots and hyphens in name", () => {
    const r = parseGitCommand("git-upload-pack '/alice/my-repo.v2'");
    expect(r).toEqual({
      service: "git-upload-pack",
      owner: "alice",
      repo: "my-repo.v2",
    });
  });

  test("trailing whitespace is ignored", () => {
    const r = parseGitCommand("git-upload-pack '/alice/repo.git'  ");
    expect(r?.owner).toBe("alice");
  });

  test("unknown service returns null", () => {
    expect(parseGitCommand("bash -i")).toBeNull();
  });

  test("empty string returns null", () => {
    expect(parseGitCommand("")).toBeNull();
  });

  test("missing owner/repo returns null", () => {
    expect(parseGitCommand("git-upload-pack")).toBeNull();
  });

  test("path traversal attempt returns null", () => {
    expect(
      parseGitCommand("git-upload-pack '/../../etc/passwd'")
    ).toBeNull();
  });

  test("shell injection attempt returns null", () => {
    expect(
      parseGitCommand("git-upload-pack '/alice/repo'; rm -rf /")
    ).toBeNull();
  });
});

// ---------------------------------------------------------------------------
// computePushedRefs
// ---------------------------------------------------------------------------

describe("computePushedRefs", () => {
  const sha1 = "a".repeat(40);
  const sha2 = "b".repeat(40);
  const sha3 = "c".repeat(40);
  const ZERO = "0".repeat(40);

  test("new branch is reported with ZERO oldSha", () => {
    const refs = computePushedRefs(
      [],
      [{ sha: sha1, ref: "refs/heads/main" }]
    );
    expect(refs).toEqual([
      { oldSha: ZERO, newSha: sha1, refName: "refs/heads/main" },
    ]);
  });

  test("updated branch reports old and new sha", () => {
    const refs = computePushedRefs(
      [{ sha: sha1, ref: "refs/heads/main" }],
      [{ sha: sha2, ref: "refs/heads/main" }]
    );
    expect(refs).toEqual([
      { oldSha: sha1, newSha: sha2, refName: "refs/heads/main" },
    ]);
  });

  test("deleted branch is reported with ZERO newSha", () => {
    const refs = computePushedRefs(
      [{ sha: sha1, ref: "refs/heads/feature" }],
      []
    );
    expect(refs).toEqual([
      { oldSha: sha1, newSha: ZERO, refName: "refs/heads/feature" },
    ]);
  });

  test("unchanged refs are not reported", () => {
    const refs = computePushedRefs(
      [
        { sha: sha1, ref: "refs/heads/main" },
        { sha: sha2, ref: "refs/heads/stable" },
      ],
      [
        { sha: sha1, ref: "refs/heads/main" },
        { sha: sha2, ref: "refs/heads/stable" },
      ]
    );
    expect(refs).toHaveLength(0);
  });

  test("multiple changes in one push", () => {
    const refs = computePushedRefs(
      [
        { sha: sha1, ref: "refs/heads/main" },
        { sha: sha2, ref: "refs/heads/old" },
      ],
      [
        { sha: sha3, ref: "refs/heads/main" },
        { sha: sha1, ref: "refs/heads/new" },
      ]
    );
    // main updated, old deleted, new created
    expect(refs).toHaveLength(3);
    const main = refs.find((r) => r.refName === "refs/heads/main");
    expect(main).toEqual({
      oldSha: sha1,
      newSha: sha3,
      refName: "refs/heads/main",
    });
    const deleted = refs.find((r) => r.refName === "refs/heads/old");
    expect(deleted?.newSha).toBe(ZERO);
    const created = refs.find((r) => r.refName === "refs/heads/new");
    expect(created?.oldSha).toBe(ZERO);
  });

  test("empty before and after is empty", () => {
    expect(computePushedRefs([], [])).toHaveLength(0);
  });

  test("tag push is included", () => {
    const refs = computePushedRefs(
      [],
      [{ sha: sha1, ref: "refs/tags/v1.0.0" }]
    );
    expect(refs[0]?.refName).toBe("refs/tags/v1.0.0");
  });
});

// ---------------------------------------------------------------------------
// resolveUserByKeyBlob — DB-gated test
// ---------------------------------------------------------------------------

describe("resolveUserByKeyBlob", () => {
  test("returns null for an unknown key blob", async () => {
    const fakeBlob = Buffer.from("not-a-real-key-blob");
    // If DB is unreachable the function fails closed and returns null.
    const result = await resolveUserByKeyBlob(fakeBlob);
    expect(result).toBeNull();
  });
});