[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:
182
domain/credentials.test.ts
Normal file
182
domain/credentials.test.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
findSyncPayloadEncryptedCredentialPaths,
|
||||
healPoisonedSecretsForMerge,
|
||||
isEncryptedCredentialPlaceholder,
|
||||
isVaultStoredKeySource,
|
||||
needsVaultStoredKeyHydration,
|
||||
stripSyncPayloadEncryptedCredentials,
|
||||
} from "./credentials.ts";
|
||||
import type { SyncPayload } from "./sync.ts";
|
||||
|
||||
const completeBlob = Buffer.alloc(19, 0);
|
||||
Buffer.from("v10", "utf8").copy(completeBlob, 0);
|
||||
const ENC = `enc:v1:${completeBlob.toString("base64")}`;
|
||||
|
||||
function samplePayload(overrides: Partial<SyncPayload> = {}): SyncPayload {
|
||||
return {
|
||||
hosts: [
|
||||
{
|
||||
id: "h1",
|
||||
label: "prod",
|
||||
hostname: "prod.example",
|
||||
username: "root",
|
||||
password: ENC,
|
||||
port: 22,
|
||||
os: "linux",
|
||||
group: "",
|
||||
tags: [],
|
||||
protocol: "ssh",
|
||||
},
|
||||
],
|
||||
keys: [
|
||||
{
|
||||
id: "k1",
|
||||
label: "key",
|
||||
type: "ED25519",
|
||||
privateKey: ENC,
|
||||
source: "imported",
|
||||
category: "key",
|
||||
created: 1,
|
||||
},
|
||||
],
|
||||
identities: [],
|
||||
snippets: [],
|
||||
customGroups: [],
|
||||
syncedAt: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("isEncryptedCredentialPlaceholder detects complete v10 device-bound ciphertext", () => {
|
||||
assert.equal(isEncryptedCredentialPlaceholder(ENC), true);
|
||||
});
|
||||
|
||||
test("isEncryptedCredentialPlaceholder rejects intermediate v10 lengths that are neither CBC nor GCM", () => {
|
||||
const body = Buffer.alloc(24, 0);
|
||||
Buffer.from("v10", "utf8").copy(body, 0);
|
||||
assert.equal(isEncryptedCredentialPlaceholder(`enc:v1:${body.toString("base64")}`), false);
|
||||
});
|
||||
|
||||
test("isEncryptedCredentialPlaceholder detects real Windows DPAPI base64 prefixes", () => {
|
||||
const body = Buffer.from([
|
||||
0x01, 0x00, 0x00, 0x00,
|
||||
0xd0, 0x8c, 0x9d, 0xdf, 0x01, 0x15, 0xd1, 0x11,
|
||||
0x8c, 0x7a, 0x00, 0xc0, 0x4f, 0xc2, 0x97, 0xeb,
|
||||
0xaa,
|
||||
]);
|
||||
const encoded = body.toString("base64");
|
||||
assert.equal(encoded.startsWith("AQAAANCMnd8"), true);
|
||||
assert.equal(isEncryptedCredentialPlaceholder(`enc:v1:${encoded}`), true);
|
||||
});
|
||||
|
||||
test("isEncryptedCredentialPlaceholder rejects header-only enc:v1 payloads", () => {
|
||||
assert.equal(isEncryptedCredentialPlaceholder("enc:v1:djEw"), false);
|
||||
});
|
||||
|
||||
test("needsVaultStoredKeyHydration waits for imported or generated ciphertext and empty keys", () => {
|
||||
assert.equal(isVaultStoredKeySource("imported"), true);
|
||||
assert.equal(isVaultStoredKeySource("generated"), true);
|
||||
assert.equal(isVaultStoredKeySource("reference"), false);
|
||||
assert.equal(needsVaultStoredKeyHydration({
|
||||
source: "imported",
|
||||
privateKey: ENC,
|
||||
}), true);
|
||||
assert.equal(needsVaultStoredKeyHydration({
|
||||
source: "generated",
|
||||
privateKey: "",
|
||||
}), true);
|
||||
assert.equal(needsVaultStoredKeyHydration({
|
||||
source: "imported",
|
||||
privateKey: "-----BEGIN OPENSSH PRIVATE KEY-----",
|
||||
}), false);
|
||||
assert.equal(needsVaultStoredKeyHydration({
|
||||
source: "reference",
|
||||
privateKey: ENC,
|
||||
}), false);
|
||||
});
|
||||
|
||||
test("findSyncPayloadEncryptedCredentialPaths reports host and key secrets", () => {
|
||||
const paths = findSyncPayloadEncryptedCredentialPaths(samplePayload());
|
||||
assert.deepEqual(paths, ["hosts[0].password", "keys[0].privateKey"]);
|
||||
});
|
||||
|
||||
test("stripSyncPayloadEncryptedCredentials clears device-bound placeholders for recovery", () => {
|
||||
const stripped = stripSyncPayloadEncryptedCredentials(samplePayload());
|
||||
assert.equal(stripped.hosts[0]?.password, undefined);
|
||||
assert.equal(stripped.keys[0]?.privateKey, "");
|
||||
assert.equal(findSyncPayloadEncryptedCredentialPaths(stripped).length, 0);
|
||||
});
|
||||
|
||||
test("healPoisonedSecretsForMerge keeps usable preferred passwords over poisoned enc:v1", () => {
|
||||
const poisoned = samplePayload();
|
||||
const preferred = samplePayload({
|
||||
hosts: [{
|
||||
...samplePayload().hosts[0]!,
|
||||
password: "preferred-secret",
|
||||
}],
|
||||
keys: [{
|
||||
...samplePayload().keys[0]!,
|
||||
privateKey: "PREFERRED_PRIVATE_KEY",
|
||||
}],
|
||||
});
|
||||
const fallback = samplePayload({
|
||||
hosts: [{
|
||||
...samplePayload().hosts[0]!,
|
||||
password: "base-secret",
|
||||
}],
|
||||
});
|
||||
const healed = healPoisonedSecretsForMerge(poisoned, preferred, fallback);
|
||||
assert.equal(healed.hosts[0]?.password, "preferred-secret");
|
||||
assert.equal(healed.keys[0]?.privateKey, "PREFERRED_PRIVATE_KEY");
|
||||
});
|
||||
|
||||
test("healPoisonedSecretsForMerge heals local poison from remote then base", () => {
|
||||
const local = samplePayload();
|
||||
const remote = samplePayload({
|
||||
hosts: [{
|
||||
...samplePayload().hosts[0]!,
|
||||
password: "remote-secret",
|
||||
}],
|
||||
keys: [{
|
||||
...samplePayload().keys[0]!,
|
||||
privateKey: ENC,
|
||||
}],
|
||||
});
|
||||
const base = samplePayload({
|
||||
keys: [{
|
||||
...samplePayload().keys[0]!,
|
||||
privateKey: "BASE_PRIVATE_KEY",
|
||||
}],
|
||||
});
|
||||
const healed = healPoisonedSecretsForMerge(local, remote, base);
|
||||
assert.equal(healed.hosts[0]?.password, "remote-secret");
|
||||
assert.equal(healed.keys[0]?.privateKey, "BASE_PRIVATE_KEY");
|
||||
});
|
||||
|
||||
test("healPoisonedSecretsForMerge preserves explicit preferred credential deletions", () => {
|
||||
const poisoned = samplePayload({
|
||||
hosts: [{
|
||||
...samplePayload().hosts[0]!,
|
||||
label: "renamed-on-poisoned-device",
|
||||
password: ENC,
|
||||
}],
|
||||
});
|
||||
const preferred = samplePayload({
|
||||
hosts: [{
|
||||
...samplePayload().hosts[0]!,
|
||||
label: "renamed-on-poisoned-device",
|
||||
password: undefined,
|
||||
}],
|
||||
});
|
||||
const fallback = samplePayload({
|
||||
hosts: [{
|
||||
...samplePayload().hosts[0]!,
|
||||
password: "base-secret",
|
||||
}],
|
||||
});
|
||||
const healed = healPoisonedSecretsForMerge(poisoned, preferred, fallback);
|
||||
assert.equal(healed.hosts[0]?.password, undefined);
|
||||
assert.equal(healed.hosts[0]?.label, "renamed-on-poisoned-device");
|
||||
});
|
||||
Reference in New Issue
Block a user