[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
125
domain/sftpDirectoryCheckpoint.test.ts
Normal file
125
domain/sftpDirectoryCheckpoint.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
accountSftpDirectoryEntries,
|
||||
appendDirectoryCheckpointIdentity,
|
||||
appendDirectoryManifestIdentity,
|
||||
claimSftpDirectoryVisit,
|
||||
createDirectoryManifestAccumulator,
|
||||
createEmptyDirectoryResumeCheckpoint,
|
||||
createSftpDirectoryBranchAncestors,
|
||||
createSftpDirectoryTraversalBudget,
|
||||
EMPTY_DIRECTORY_MANIFEST_HASH,
|
||||
isValidDirectoryResumeCheckpoint,
|
||||
MAX_SFTP_FOLLOWED_SYMLINK_DEPTH,
|
||||
releaseSftpDirectoryVisit,
|
||||
shouldFollowSftpSymlinkDirectory,
|
||||
} from "./sftpDirectoryCheckpoint";
|
||||
|
||||
test("versioned directory manifests detect changed and reordered entries", () => {
|
||||
const first = "a".repeat(64);
|
||||
const second = "b".repeat(64);
|
||||
const changed = "c".repeat(64);
|
||||
const build = (identities: string[]) => {
|
||||
const checkpoint = createEmptyDirectoryResumeCheckpoint();
|
||||
for (const identity of identities) {
|
||||
checkpoint.manifestHash = appendDirectoryCheckpointIdentity(checkpoint, identity);
|
||||
checkpoint.coveredEntries += 1;
|
||||
}
|
||||
return checkpoint;
|
||||
};
|
||||
|
||||
const original = build([first, second]);
|
||||
const batched = createDirectoryManifestAccumulator(createEmptyDirectoryResumeCheckpoint());
|
||||
batched.append(first);
|
||||
batched.append(second);
|
||||
assert.equal(original.version, 2);
|
||||
assert.equal(original.manifestHash, batched.digest());
|
||||
assert.equal(original.manifestHash, build([first, second]).manifestHash);
|
||||
assert.notEqual(original.manifestHash, build([first, changed]).manifestHash);
|
||||
assert.notEqual(original.manifestHash, build([second, first]).manifestHash);
|
||||
});
|
||||
|
||||
test("legacy chained manifests remain valid and append-compatible", () => {
|
||||
const legacy = {
|
||||
version: 1 as const,
|
||||
coveredEntries: 1,
|
||||
completedEntries: 1,
|
||||
manifestHash: appendDirectoryManifestIdentity(
|
||||
EMPTY_DIRECTORY_MANIFEST_HASH,
|
||||
"a".repeat(64),
|
||||
),
|
||||
};
|
||||
assert.equal(isValidDirectoryResumeCheckpoint(legacy), true);
|
||||
assert.equal(
|
||||
legacy.manifestHash,
|
||||
createHash("sha256")
|
||||
.update(`${EMPTY_DIRECTORY_MANIFEST_HASH}:${"a".repeat(64)}`)
|
||||
.digest("hex"),
|
||||
);
|
||||
assert.equal(
|
||||
appendDirectoryCheckpointIdentity(legacy, "b".repeat(64)),
|
||||
appendDirectoryManifestIdentity(legacy.manifestHash, "b".repeat(64)),
|
||||
);
|
||||
});
|
||||
|
||||
test("live and resumed directory transfers share the same symlink depth policy", () => {
|
||||
assert.equal(MAX_SFTP_FOLLOWED_SYMLINK_DEPTH, 32);
|
||||
assert.equal(shouldFollowSftpSymlinkDirectory(8), true);
|
||||
assert.equal(shouldFollowSftpSymlinkDirectory(31), true);
|
||||
assert.equal(shouldFollowSftpSymlinkDirectory(32), false);
|
||||
});
|
||||
|
||||
test("remote directory traversal rejects ancestor cycles but allows sibling aliases", () => {
|
||||
const budget = createSftpDirectoryTraversalBudget({ maxDirectories: 3, maxEntries: 3 });
|
||||
const root = claimSftpDirectoryVisit(budget, "/srv/root");
|
||||
assert.ok(root);
|
||||
accountSftpDirectoryEntries(budget, 2);
|
||||
// Re-entering the same canonical path on the current branch is a cycle.
|
||||
assert.equal(claimSftpDirectoryVisit(budget, "/srv/root"), null);
|
||||
// Distinct symlink aliases to one target must both be traversed.
|
||||
const aliasA = claimSftpDirectoryVisit(budget, "/srv/shared");
|
||||
assert.ok(aliasA);
|
||||
releaseSftpDirectoryVisit(budget, aliasA);
|
||||
const aliasB = claimSftpDirectoryVisit(budget, "/srv/shared");
|
||||
assert.ok(aliasB);
|
||||
releaseSftpDirectoryVisit(budget, aliasB);
|
||||
assert.throws(() => accountSftpDirectoryEntries(budget, 2), /entry limit/i);
|
||||
assert.throws(() => claimSftpDirectoryVisit(budget, "/srv/third"), /directory limit/i);
|
||||
releaseSftpDirectoryVisit(budget, root);
|
||||
assert.equal(budget.activeCanonicalDirectories.size, 0);
|
||||
});
|
||||
|
||||
test("parallel sibling branches may both claim the same canonical alias", () => {
|
||||
const budget = createSftpDirectoryTraversalBudget({ maxDirectories: 8, maxEntries: 8 });
|
||||
const rootAncestors = createSftpDirectoryBranchAncestors();
|
||||
const root = claimSftpDirectoryVisit(budget, "/srv/root", rootAncestors);
|
||||
assert.ok(root);
|
||||
const branchA = createSftpDirectoryBranchAncestors(rootAncestors);
|
||||
const branchB = createSftpDirectoryBranchAncestors(rootAncestors);
|
||||
const aliasA = claimSftpDirectoryVisit(budget, "/srv/shared", branchA);
|
||||
const aliasB = claimSftpDirectoryVisit(budget, "/srv/shared", branchB);
|
||||
assert.ok(aliasA);
|
||||
assert.ok(aliasB);
|
||||
assert.equal(claimSftpDirectoryVisit(budget, "/srv/root", branchA), null);
|
||||
assert.equal(claimSftpDirectoryVisit(budget, "/srv/root", branchB), null);
|
||||
releaseSftpDirectoryVisit(budget, aliasA, branchA);
|
||||
releaseSftpDirectoryVisit(budget, aliasB, branchB);
|
||||
releaseSftpDirectoryVisit(budget, root, rootAncestors);
|
||||
});
|
||||
|
||||
test("remote directory traversal releases sibling visits without retaining history", () => {
|
||||
const budget = createSftpDirectoryTraversalBudget({ maxDirectories: 50_000 });
|
||||
const root = claimSftpDirectoryVisit(budget, "/srv/root");
|
||||
assert.ok(root);
|
||||
for (let index = 1; index < 50_000; index += 1) {
|
||||
const sibling = claimSftpDirectoryVisit(budget, `/srv/root/alias-${index}`);
|
||||
assert.ok(sibling);
|
||||
releaseSftpDirectoryVisit(budget, sibling);
|
||||
}
|
||||
assert.equal(budget.activeCanonicalDirectories.size, 1);
|
||||
releaseSftpDirectoryVisit(budget, root);
|
||||
assert.equal(budget.activeCanonicalDirectories.size, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user