ctx-sync 1.0.0
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/commands/audit.d.ts +76 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +367 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/config.d.ts +58 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +114 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/dir.d.ts +56 -0
- package/dist/commands/dir.d.ts.map +1 -0
- package/dist/commands/dir.js +172 -0
- package/dist/commands/dir.js.map +1 -0
- package/dist/commands/docker.d.ts +140 -0
- package/dist/commands/docker.d.ts.map +1 -0
- package/dist/commands/docker.js +380 -0
- package/dist/commands/docker.js.map +1 -0
- package/dist/commands/env.d.ts +96 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +352 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/init.d.ts +89 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/key.d.ts +92 -0
- package/dist/commands/key.d.ts.map +1 -0
- package/dist/commands/key.js +274 -0
- package/dist/commands/key.js.map +1 -0
- package/dist/commands/list.d.ts +38 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +84 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/note.d.ts +151 -0
- package/dist/commands/note.d.ts.map +1 -0
- package/dist/commands/note.js +411 -0
- package/dist/commands/note.js.map +1 -0
- package/dist/commands/pull.d.ts +47 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +94 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +40 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +94 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/restore.d.ts +116 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +336 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/service.d.ts +83 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +259 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/show.d.ts +63 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +243 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +53 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +150 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +105 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +243 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/team.d.ts +79 -0
- package/dist/commands/team.d.ts.map +1 -0
- package/dist/commands/team.js +233 -0
- package/dist/commands/team.js.map +1 -0
- package/dist/commands/track.d.ts +109 -0
- package/dist/commands/track.d.ts.map +1 -0
- package/dist/commands/track.js +406 -0
- package/dist/commands/track.js.map +1 -0
- package/dist/core/command-validator.d.ts +100 -0
- package/dist/core/command-validator.d.ts.map +1 -0
- package/dist/core/command-validator.js +299 -0
- package/dist/core/command-validator.js.map +1 -0
- package/dist/core/config-store.d.ts +76 -0
- package/dist/core/config-store.d.ts.map +1 -0
- package/dist/core/config-store.js +148 -0
- package/dist/core/config-store.js.map +1 -0
- package/dist/core/directories-handler.d.ts +116 -0
- package/dist/core/directories-handler.d.ts.map +1 -0
- package/dist/core/directories-handler.js +199 -0
- package/dist/core/directories-handler.js.map +1 -0
- package/dist/core/docker-handler.d.ts +183 -0
- package/dist/core/docker-handler.d.ts.map +1 -0
- package/dist/core/docker-handler.js +515 -0
- package/dist/core/docker-handler.js.map +1 -0
- package/dist/core/encryption.d.ts +79 -0
- package/dist/core/encryption.d.ts.map +1 -0
- package/dist/core/encryption.js +111 -0
- package/dist/core/encryption.js.map +1 -0
- package/dist/core/env-handler.d.ts +128 -0
- package/dist/core/env-handler.d.ts.map +1 -0
- package/dist/core/env-handler.js +272 -0
- package/dist/core/env-handler.js.map +1 -0
- package/dist/core/git-sync.d.ts +88 -0
- package/dist/core/git-sync.d.ts.map +1 -0
- package/dist/core/git-sync.js +143 -0
- package/dist/core/git-sync.js.map +1 -0
- package/dist/core/key-store.d.ts +51 -0
- package/dist/core/key-store.d.ts.map +1 -0
- package/dist/core/key-store.js +108 -0
- package/dist/core/key-store.js.map +1 -0
- package/dist/core/log-sanitizer.d.ts +72 -0
- package/dist/core/log-sanitizer.d.ts.map +1 -0
- package/dist/core/log-sanitizer.js +202 -0
- package/dist/core/log-sanitizer.js.map +1 -0
- package/dist/core/path-validator.d.ts +37 -0
- package/dist/core/path-validator.d.ts.map +1 -0
- package/dist/core/path-validator.js +127 -0
- package/dist/core/path-validator.js.map +1 -0
- package/dist/core/recipients.d.ts +99 -0
- package/dist/core/recipients.d.ts.map +1 -0
- package/dist/core/recipients.js +206 -0
- package/dist/core/recipients.js.map +1 -0
- package/dist/core/services-handler.d.ts +113 -0
- package/dist/core/services-handler.d.ts.map +1 -0
- package/dist/core/services-handler.js +176 -0
- package/dist/core/services-handler.js.map +1 -0
- package/dist/core/state-manager.d.ts +96 -0
- package/dist/core/state-manager.d.ts.map +1 -0
- package/dist/core/state-manager.js +165 -0
- package/dist/core/state-manager.js.map +1 -0
- package/dist/core/transport.d.ts +28 -0
- package/dist/core/transport.d.ts.map +1 -0
- package/dist/core/transport.js +79 -0
- package/dist/core/transport.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +81 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +191 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/secure-memory.d.ts +65 -0
- package/dist/utils/secure-memory.d.ts.map +1 -0
- package/dist/utils/secure-memory.js +86 -0
- package/dist/utils/secure-memory.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git sync engine module.
|
|
3
|
+
*
|
|
4
|
+
* Manages the local Git repository used for syncing encrypted state
|
|
5
|
+
* files between machines. Provides operations for init, commit, push,
|
|
6
|
+
* pull, and status queries. All remote operations validate transport
|
|
7
|
+
* security before proceeding.
|
|
8
|
+
*
|
|
9
|
+
* @module core/git-sync
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { simpleGit } from 'simple-git';
|
|
14
|
+
import { validateRemoteUrl } from './transport.js';
|
|
15
|
+
/**
|
|
16
|
+
* Initialise a Git repository in the given directory.
|
|
17
|
+
*
|
|
18
|
+
* If the directory already contains a `.git/` folder, this is a no-op.
|
|
19
|
+
* Otherwise, a new Git repository is created with `git init`.
|
|
20
|
+
*
|
|
21
|
+
* @param dir - The directory to initialise.
|
|
22
|
+
* @returns `true` if a new repo was created, `false` if one already existed.
|
|
23
|
+
*/
|
|
24
|
+
export async function initRepo(dir) {
|
|
25
|
+
const gitDir = path.join(dir, '.git');
|
|
26
|
+
if (fs.existsSync(gitDir)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// Ensure the directory exists
|
|
30
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
31
|
+
const git = simpleGit(dir);
|
|
32
|
+
await git.init();
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Add a remote to the Git repository.
|
|
37
|
+
*
|
|
38
|
+
* Validates the URL using transport security before adding.
|
|
39
|
+
* If a remote with the given name already exists, it is updated.
|
|
40
|
+
*
|
|
41
|
+
* @param dir - The Git repository directory.
|
|
42
|
+
* @param url - The remote URL (SSH or HTTPS).
|
|
43
|
+
* @param remoteName - The name for the remote (default: 'origin').
|
|
44
|
+
* @throws If the URL uses an insecure protocol.
|
|
45
|
+
*/
|
|
46
|
+
export async function addRemote(dir, url, remoteName = 'origin') {
|
|
47
|
+
validateRemoteUrl(url);
|
|
48
|
+
const git = simpleGit(dir);
|
|
49
|
+
const remotes = await git.getRemotes(true);
|
|
50
|
+
const existing = remotes.find((r) => r.name === remoteName);
|
|
51
|
+
if (existing) {
|
|
52
|
+
await git.remote(['set-url', remoteName, url]);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await git.addRemote(remoteName, url);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Stage files and commit changes to the sync repository.
|
|
60
|
+
*
|
|
61
|
+
* Stages the specified files, then commits with the given message.
|
|
62
|
+
* If there are no changes to commit (working tree is clean after staging),
|
|
63
|
+
* the commit is skipped and `null` is returned.
|
|
64
|
+
*
|
|
65
|
+
* @param dir - The Git repository directory.
|
|
66
|
+
* @param files - List of file paths (relative to dir) to stage.
|
|
67
|
+
* @param message - The commit message.
|
|
68
|
+
* @returns The commit hash, or `null` if no changes were committed.
|
|
69
|
+
*/
|
|
70
|
+
export async function commitState(dir, files, message) {
|
|
71
|
+
const git = simpleGit(dir);
|
|
72
|
+
// Stage specified files
|
|
73
|
+
await git.add(files);
|
|
74
|
+
// Check if there are staged changes
|
|
75
|
+
const status = await git.status();
|
|
76
|
+
const hasStagedChanges = status.staged.length > 0 || status.created.length > 0 || status.deleted.length > 0;
|
|
77
|
+
if (!hasStagedChanges) {
|
|
78
|
+
return null; // Nothing to commit
|
|
79
|
+
}
|
|
80
|
+
const result = await git.commit(message);
|
|
81
|
+
return result.commit || null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Push the local sync repository to the remote.
|
|
85
|
+
*
|
|
86
|
+
* Validates the remote URL before pushing. If no remote is configured,
|
|
87
|
+
* the push is skipped.
|
|
88
|
+
*
|
|
89
|
+
* @param dir - The Git repository directory.
|
|
90
|
+
* @param remoteName - The remote name (default: 'origin').
|
|
91
|
+
* @param branch - The branch to push (default: 'main').
|
|
92
|
+
* @throws If the remote URL uses an insecure protocol.
|
|
93
|
+
*/
|
|
94
|
+
export async function pushState(dir, remoteName = 'origin', branch = 'main') {
|
|
95
|
+
const git = simpleGit(dir);
|
|
96
|
+
// Verify remote exists and URL is secure
|
|
97
|
+
const remotes = await git.getRemotes(true);
|
|
98
|
+
const remote = remotes.find((r) => r.name === remoteName);
|
|
99
|
+
if (!remote) {
|
|
100
|
+
return; // No remote configured — local only
|
|
101
|
+
}
|
|
102
|
+
validateRemoteUrl(remote.refs.push || remote.refs.fetch);
|
|
103
|
+
await git.push(remoteName, branch, ['--set-upstream']);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Pull the latest changes from the remote sync repository.
|
|
107
|
+
*
|
|
108
|
+
* Validates the remote URL before pulling. If no remote is configured,
|
|
109
|
+
* the pull is skipped.
|
|
110
|
+
*
|
|
111
|
+
* @param dir - The Git repository directory.
|
|
112
|
+
* @param remoteName - The remote name (default: 'origin').
|
|
113
|
+
* @param branch - The branch to pull (default: 'main').
|
|
114
|
+
* @throws If the remote URL uses an insecure protocol.
|
|
115
|
+
*/
|
|
116
|
+
export async function pullState(dir, remoteName = 'origin', branch = 'main') {
|
|
117
|
+
const git = simpleGit(dir);
|
|
118
|
+
// Verify remote exists and URL is secure
|
|
119
|
+
const remotes = await git.getRemotes(true);
|
|
120
|
+
const remote = remotes.find((r) => r.name === remoteName);
|
|
121
|
+
if (!remote) {
|
|
122
|
+
return; // No remote configured — local only
|
|
123
|
+
}
|
|
124
|
+
validateRemoteUrl(remote.refs.fetch || remote.refs.push);
|
|
125
|
+
await git.pull(remoteName, branch);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get the current sync status of the repository.
|
|
129
|
+
*
|
|
130
|
+
* @param dir - The Git repository directory.
|
|
131
|
+
* @returns An object describing changed files, ahead/behind counts, and cleanliness.
|
|
132
|
+
*/
|
|
133
|
+
export async function getStatus(dir) {
|
|
134
|
+
const git = simpleGit(dir);
|
|
135
|
+
const status = await git.status();
|
|
136
|
+
return {
|
|
137
|
+
files: status.files.map((f) => f.path),
|
|
138
|
+
ahead: status.ahead,
|
|
139
|
+
behind: status.behind,
|
|
140
|
+
isClean: status.isClean(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=git-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-sync.js","sourceRoot":"","sources":["../../src/core/git-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key persistence module.
|
|
3
|
+
*
|
|
4
|
+
* Handles saving and loading the Age private key to/from disk
|
|
5
|
+
* with strict file permission enforcement. The private key file
|
|
6
|
+
* is stored at 0o600 (owner read/write only) and the config
|
|
7
|
+
* directory at 0o700 (owner read/write/execute only).
|
|
8
|
+
*
|
|
9
|
+
* @module core/key-store
|
|
10
|
+
*/
|
|
11
|
+
/** Required permissions for the private key file */
|
|
12
|
+
export declare const KEY_FILE_PERMS = 384;
|
|
13
|
+
/** Required permissions for the config directory */
|
|
14
|
+
export declare const CONFIG_DIR_PERMS = 448;
|
|
15
|
+
/** Default key file name */
|
|
16
|
+
export declare const KEY_FILE_NAME = "key.txt";
|
|
17
|
+
/**
|
|
18
|
+
* Save a private key to disk with strict permissions.
|
|
19
|
+
*
|
|
20
|
+
* Creates the config directory with 0o700 permissions if it does not exist,
|
|
21
|
+
* then writes the key file with 0o600 permissions.
|
|
22
|
+
*
|
|
23
|
+
* @param configDir - The config directory path (e.g. ~/.config/ctx-sync).
|
|
24
|
+
* @param privateKey - The Age private key string (AGE-SECRET-KEY-...).
|
|
25
|
+
*/
|
|
26
|
+
export declare function saveKey(configDir: string, privateKey: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Load a private key from disk, verifying permissions are secure.
|
|
29
|
+
*
|
|
30
|
+
* Checks that the key file exists and has exactly 0o600 permissions
|
|
31
|
+
* before reading. Throws with a helpful error if permissions are insecure.
|
|
32
|
+
*
|
|
33
|
+
* @param configDir - The config directory path (e.g. ~/.config/ctx-sync).
|
|
34
|
+
* @returns The Age private key string.
|
|
35
|
+
* @throws If the key file does not exist or has insecure permissions.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadKey(configDir: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Verify that key file and config directory have correct permissions.
|
|
40
|
+
*
|
|
41
|
+
* @param configDir - The config directory path.
|
|
42
|
+
* @returns An object describing the verification result.
|
|
43
|
+
*/
|
|
44
|
+
export declare function verifyPermissions(configDir: string): {
|
|
45
|
+
valid: boolean;
|
|
46
|
+
keyFileExists: boolean;
|
|
47
|
+
keyFilePerms: number | null;
|
|
48
|
+
configDirPerms: number | null;
|
|
49
|
+
issues: string[];
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=key-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-store.d.ts","sourceRoot":"","sources":["../../src/core/key-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,oDAAoD;AACpD,eAAO,MAAM,cAAc,MAAQ,CAAC;AAEpC,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AAEtC,4BAA4B;AAC5B,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CASnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAwBjD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG;IACpD,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CA4CA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key persistence module.
|
|
3
|
+
*
|
|
4
|
+
* Handles saving and loading the Age private key to/from disk
|
|
5
|
+
* with strict file permission enforcement. The private key file
|
|
6
|
+
* is stored at 0o600 (owner read/write only) and the config
|
|
7
|
+
* directory at 0o700 (owner read/write/execute only).
|
|
8
|
+
*
|
|
9
|
+
* @module core/key-store
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
/** Required permissions for the private key file */
|
|
14
|
+
export const KEY_FILE_PERMS = 0o600;
|
|
15
|
+
/** Required permissions for the config directory */
|
|
16
|
+
export const CONFIG_DIR_PERMS = 0o700;
|
|
17
|
+
/** Default key file name */
|
|
18
|
+
export const KEY_FILE_NAME = 'key.txt';
|
|
19
|
+
/**
|
|
20
|
+
* Save a private key to disk with strict permissions.
|
|
21
|
+
*
|
|
22
|
+
* Creates the config directory with 0o700 permissions if it does not exist,
|
|
23
|
+
* then writes the key file with 0o600 permissions.
|
|
24
|
+
*
|
|
25
|
+
* @param configDir - The config directory path (e.g. ~/.config/ctx-sync).
|
|
26
|
+
* @param privateKey - The Age private key string (AGE-SECRET-KEY-...).
|
|
27
|
+
*/
|
|
28
|
+
export function saveKey(configDir, privateKey) {
|
|
29
|
+
// Create directory with 0o700 if it doesn't exist
|
|
30
|
+
fs.mkdirSync(configDir, { recursive: true, mode: CONFIG_DIR_PERMS });
|
|
31
|
+
// Ensure directory has correct permissions even if it already existed
|
|
32
|
+
fs.chmodSync(configDir, CONFIG_DIR_PERMS);
|
|
33
|
+
const keyPath = path.join(configDir, KEY_FILE_NAME);
|
|
34
|
+
fs.writeFileSync(keyPath, privateKey, { mode: KEY_FILE_PERMS });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Load a private key from disk, verifying permissions are secure.
|
|
38
|
+
*
|
|
39
|
+
* Checks that the key file exists and has exactly 0o600 permissions
|
|
40
|
+
* before reading. Throws with a helpful error if permissions are insecure.
|
|
41
|
+
*
|
|
42
|
+
* @param configDir - The config directory path (e.g. ~/.config/ctx-sync).
|
|
43
|
+
* @returns The Age private key string.
|
|
44
|
+
* @throws If the key file does not exist or has insecure permissions.
|
|
45
|
+
*/
|
|
46
|
+
export function loadKey(configDir) {
|
|
47
|
+
const keyPath = path.join(configDir, KEY_FILE_NAME);
|
|
48
|
+
// Check file exists
|
|
49
|
+
if (!fs.existsSync(keyPath)) {
|
|
50
|
+
throw new Error(`Key file not found: ${keyPath}\n` +
|
|
51
|
+
'Run `ctx-sync init` to generate an encryption key, or\n' +
|
|
52
|
+
'Run `ctx-sync init --restore` to restore from a backup.');
|
|
53
|
+
}
|
|
54
|
+
// Verify permissions before reading
|
|
55
|
+
const stats = fs.statSync(keyPath);
|
|
56
|
+
const mode = stats.mode & 0o777;
|
|
57
|
+
if (mode !== KEY_FILE_PERMS) {
|
|
58
|
+
throw new Error(`Key file has insecure permissions (${mode.toString(8)}). Expected 600.\n` +
|
|
59
|
+
`Fix with: chmod 600 ${keyPath}`);
|
|
60
|
+
}
|
|
61
|
+
return fs.readFileSync(keyPath, 'utf-8').trim();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verify that key file and config directory have correct permissions.
|
|
65
|
+
*
|
|
66
|
+
* @param configDir - The config directory path.
|
|
67
|
+
* @returns An object describing the verification result.
|
|
68
|
+
*/
|
|
69
|
+
export function verifyPermissions(configDir) {
|
|
70
|
+
const issues = [];
|
|
71
|
+
const keyPath = path.join(configDir, KEY_FILE_NAME);
|
|
72
|
+
let keyFileExists = false;
|
|
73
|
+
let keyFilePerms = null;
|
|
74
|
+
let configDirPerms = null;
|
|
75
|
+
// Check config directory
|
|
76
|
+
if (fs.existsSync(configDir)) {
|
|
77
|
+
const dirStats = fs.statSync(configDir);
|
|
78
|
+
configDirPerms = dirStats.mode & 0o777;
|
|
79
|
+
if (configDirPerms !== CONFIG_DIR_PERMS) {
|
|
80
|
+
issues.push(`Config directory has permissions ${configDirPerms.toString(8)}, expected 700. ` +
|
|
81
|
+
`Fix with: chmod 700 ${configDir}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
issues.push(`Config directory does not exist: ${configDir}`);
|
|
86
|
+
}
|
|
87
|
+
// Check key file
|
|
88
|
+
if (fs.existsSync(keyPath)) {
|
|
89
|
+
keyFileExists = true;
|
|
90
|
+
const fileStats = fs.statSync(keyPath);
|
|
91
|
+
keyFilePerms = fileStats.mode & 0o777;
|
|
92
|
+
if (keyFilePerms !== KEY_FILE_PERMS) {
|
|
93
|
+
issues.push(`Key file has permissions ${keyFilePerms.toString(8)}, expected 600. ` +
|
|
94
|
+
`Fix with: chmod 600 ${keyPath}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
issues.push(`Key file not found: ${keyPath}`);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
valid: issues.length === 0,
|
|
102
|
+
keyFileExists,
|
|
103
|
+
keyFilePerms,
|
|
104
|
+
configDirPerms,
|
|
105
|
+
issues,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=key-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-store.js","sourceRoot":"","sources":["../../src/core/key-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log sanitizer module.
|
|
3
|
+
*
|
|
4
|
+
* Redacts known secret patterns from log messages, error messages,
|
|
5
|
+
* and stack traces. Wraps console output to ensure secrets never
|
|
6
|
+
* appear in logs — even when `DEBUG=*` is set.
|
|
7
|
+
*
|
|
8
|
+
* **Security property:** No secret value ever appears in any log output.
|
|
9
|
+
*
|
|
10
|
+
* Patterns detected and redacted:
|
|
11
|
+
* - Stripe keys (`sk_live_*`, `sk_test_*`)
|
|
12
|
+
* - GitHub PATs (`ghp_*`, `gho_*`, `github_pat_*`)
|
|
13
|
+
* - Slack tokens (`xoxb-*`, `xoxp-*`)
|
|
14
|
+
* - Google API keys (`AIzaSy*`)
|
|
15
|
+
* - AWS access keys (`AKIA*`)
|
|
16
|
+
* - SendGrid keys (`SG.*`)
|
|
17
|
+
* - Twilio SIDs (`AC` + 32 hex)
|
|
18
|
+
* - OpenAI keys (`sk-*` 20+ chars)
|
|
19
|
+
* - Age secret keys (`AGE-SECRET-KEY-*`)
|
|
20
|
+
* - JWTs (`eyJ*...*`)
|
|
21
|
+
* - PEM private keys
|
|
22
|
+
* - URLs with embedded credentials
|
|
23
|
+
* - Generic `password=`, `secret=`, `token=`, `key=` patterns
|
|
24
|
+
*
|
|
25
|
+
* @module core/log-sanitizer
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Sanitize a message by redacting known secret patterns.
|
|
29
|
+
*
|
|
30
|
+
* Applies all known secret pattern regexes and replaces matches
|
|
31
|
+
* with redacted placeholders. Safe values pass through unchanged.
|
|
32
|
+
*
|
|
33
|
+
* @param message - The log message to sanitize.
|
|
34
|
+
* @returns The sanitized message with secrets redacted.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sanitizeForLog(message: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Sanitize an error object's message and stack trace.
|
|
39
|
+
*
|
|
40
|
+
* Returns a new Error with sanitized message and stack. The original
|
|
41
|
+
* error is not modified.
|
|
42
|
+
*
|
|
43
|
+
* @param error - The error to sanitize.
|
|
44
|
+
* @returns A new Error with sanitized message and stack.
|
|
45
|
+
*/
|
|
46
|
+
export declare function sanitizeError(error: Error): Error;
|
|
47
|
+
/**
|
|
48
|
+
* Wrap all console output methods through the sanitizer.
|
|
49
|
+
*
|
|
50
|
+
* After calling this function, all `console.log`, `console.error`,
|
|
51
|
+
* `console.warn`, and `console.debug` calls will have their arguments
|
|
52
|
+
* sanitized before output.
|
|
53
|
+
*
|
|
54
|
+
* Call `unwrapConsole()` to restore original behaviour.
|
|
55
|
+
*
|
|
56
|
+
* **Security:** This ensures that even if a developer accidentally
|
|
57
|
+
* logs a secret value, it will be redacted before reaching the terminal.
|
|
58
|
+
*/
|
|
59
|
+
export declare function wrapConsole(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Restore original console methods (unwrap the sanitizer).
|
|
62
|
+
*
|
|
63
|
+
* Safe to call even if `wrapConsole()` was never called.
|
|
64
|
+
*/
|
|
65
|
+
export declare function unwrapConsole(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Check whether the console is currently wrapped by the sanitizer.
|
|
68
|
+
*
|
|
69
|
+
* @returns `true` if `wrapConsole()` has been called and `unwrapConsole()` has not.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isConsoleWrapped(): boolean;
|
|
72
|
+
//# sourceMappingURL=log-sanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-sanitizer.d.ts","sourceRoot":"","sources":["../../src/core/log-sanitizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAkEH;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CActD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAOjD;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,IAAI,CA0ClC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAgBpC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log sanitizer module.
|
|
3
|
+
*
|
|
4
|
+
* Redacts known secret patterns from log messages, error messages,
|
|
5
|
+
* and stack traces. Wraps console output to ensure secrets never
|
|
6
|
+
* appear in logs — even when `DEBUG=*` is set.
|
|
7
|
+
*
|
|
8
|
+
* **Security property:** No secret value ever appears in any log output.
|
|
9
|
+
*
|
|
10
|
+
* Patterns detected and redacted:
|
|
11
|
+
* - Stripe keys (`sk_live_*`, `sk_test_*`)
|
|
12
|
+
* - GitHub PATs (`ghp_*`, `gho_*`, `github_pat_*`)
|
|
13
|
+
* - Slack tokens (`xoxb-*`, `xoxp-*`)
|
|
14
|
+
* - Google API keys (`AIzaSy*`)
|
|
15
|
+
* - AWS access keys (`AKIA*`)
|
|
16
|
+
* - SendGrid keys (`SG.*`)
|
|
17
|
+
* - Twilio SIDs (`AC` + 32 hex)
|
|
18
|
+
* - OpenAI keys (`sk-*` 20+ chars)
|
|
19
|
+
* - Age secret keys (`AGE-SECRET-KEY-*`)
|
|
20
|
+
* - JWTs (`eyJ*...*`)
|
|
21
|
+
* - PEM private keys
|
|
22
|
+
* - URLs with embedded credentials
|
|
23
|
+
* - Generic `password=`, `secret=`, `token=`, `key=` patterns
|
|
24
|
+
*
|
|
25
|
+
* @module core/log-sanitizer
|
|
26
|
+
*/
|
|
27
|
+
const REDACTED = '***REDACTED***';
|
|
28
|
+
/**
|
|
29
|
+
* Secret patterns to redact from log output.
|
|
30
|
+
*
|
|
31
|
+
* Each entry maps a regex to a replacement function. The regex
|
|
32
|
+
* should match the full secret token; the replacement function
|
|
33
|
+
* returns the redacted version (preserving a prefix for debugging
|
|
34
|
+
* context where possible).
|
|
35
|
+
*/
|
|
36
|
+
const SECRET_PATTERNS = [
|
|
37
|
+
// Stripe keys
|
|
38
|
+
{ pattern: /\bsk_live_[a-zA-Z0-9_]+/g, replacement: `sk_live_${REDACTED}` },
|
|
39
|
+
{ pattern: /\bsk_test_[a-zA-Z0-9_]+/g, replacement: `sk_test_${REDACTED}` },
|
|
40
|
+
// GitHub tokens
|
|
41
|
+
{ pattern: /\bghp_[a-zA-Z0-9]+/g, replacement: `ghp_${REDACTED}` },
|
|
42
|
+
{ pattern: /\bgho_[a-zA-Z0-9]+/g, replacement: `gho_${REDACTED}` },
|
|
43
|
+
{ pattern: /\bgithub_pat_[a-zA-Z0-9_]+/g, replacement: `github_pat_${REDACTED}` },
|
|
44
|
+
// Slack tokens
|
|
45
|
+
{ pattern: /\bxoxb-[a-zA-Z0-9-]+/g, replacement: `xoxb-${REDACTED}` },
|
|
46
|
+
{ pattern: /\bxoxp-[a-zA-Z0-9-]+/g, replacement: `xoxp-${REDACTED}` },
|
|
47
|
+
// Google API keys
|
|
48
|
+
{ pattern: /\bAIzaSy[a-zA-Z0-9_-]+/g, replacement: `AIzaSy${REDACTED}` },
|
|
49
|
+
// AWS access keys
|
|
50
|
+
{ pattern: /\bAKIA[A-Z0-9]{16,}/g, replacement: `AKIA${REDACTED}` },
|
|
51
|
+
// SendGrid keys
|
|
52
|
+
{ pattern: /\bSG\.[a-zA-Z0-9_.-]+/g, replacement: `SG.${REDACTED}` },
|
|
53
|
+
// Twilio SIDs
|
|
54
|
+
{ pattern: /\bAC[a-f0-9]{32}/g, replacement: `AC${REDACTED}` },
|
|
55
|
+
// OpenAI keys
|
|
56
|
+
{ pattern: /\bsk-[a-zA-Z0-9]{20,}/g, replacement: `sk-${REDACTED}` },
|
|
57
|
+
// Age secret keys (critical — NEVER log private keys)
|
|
58
|
+
{ pattern: /AGE-SECRET-KEY-[A-Z0-9]+/g, replacement: `AGE-SECRET-KEY-${REDACTED}` },
|
|
59
|
+
// JWTs (header.payload.signature)
|
|
60
|
+
{ pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, replacement: `eyJ${REDACTED}` },
|
|
61
|
+
// PEM private keys (multiline — match the whole block)
|
|
62
|
+
{
|
|
63
|
+
pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE KEY-----[\s\S]*?-----END\s+(?:RSA\s+)?PRIVATE KEY-----/g,
|
|
64
|
+
replacement: `-----BEGIN PRIVATE KEY-----\n${REDACTED}\n-----END PRIVATE KEY-----`,
|
|
65
|
+
},
|
|
66
|
+
// PEM certificates
|
|
67
|
+
{
|
|
68
|
+
pattern: /-----BEGIN\s+CERTIFICATE-----[\s\S]*?-----END\s+CERTIFICATE-----/g,
|
|
69
|
+
replacement: `-----BEGIN CERTIFICATE-----\n${REDACTED}\n-----END CERTIFICATE-----`,
|
|
70
|
+
},
|
|
71
|
+
// URLs with embedded credentials (e.g. postgres://user:pass@host/db, redis://:pass@host)
|
|
72
|
+
{ pattern: /:\/\/[^:/?#\s]*:[^@/?#\s]+@/g, replacement: `://${REDACTED}:${REDACTED}@` },
|
|
73
|
+
// Generic key=value patterns for common secret field names
|
|
74
|
+
{ pattern: /(\b(?:password|secret|token|key|apikey|api_key|auth)\s*=\s*)\S+/gi, replacement: `$1${REDACTED}` },
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* Sanitize a message by redacting known secret patterns.
|
|
78
|
+
*
|
|
79
|
+
* Applies all known secret pattern regexes and replaces matches
|
|
80
|
+
* with redacted placeholders. Safe values pass through unchanged.
|
|
81
|
+
*
|
|
82
|
+
* @param message - The log message to sanitize.
|
|
83
|
+
* @returns The sanitized message with secrets redacted.
|
|
84
|
+
*/
|
|
85
|
+
export function sanitizeForLog(message) {
|
|
86
|
+
if (!message || typeof message !== 'string') {
|
|
87
|
+
return message;
|
|
88
|
+
}
|
|
89
|
+
let sanitized = message;
|
|
90
|
+
for (const { pattern, replacement } of SECRET_PATTERNS) {
|
|
91
|
+
// Reset the regex lastIndex (global regexes are stateful)
|
|
92
|
+
pattern.lastIndex = 0;
|
|
93
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
94
|
+
}
|
|
95
|
+
return sanitized;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Sanitize an error object's message and stack trace.
|
|
99
|
+
*
|
|
100
|
+
* Returns a new Error with sanitized message and stack. The original
|
|
101
|
+
* error is not modified.
|
|
102
|
+
*
|
|
103
|
+
* @param error - The error to sanitize.
|
|
104
|
+
* @returns A new Error with sanitized message and stack.
|
|
105
|
+
*/
|
|
106
|
+
export function sanitizeError(error) {
|
|
107
|
+
const sanitized = new Error(sanitizeForLog(error.message));
|
|
108
|
+
if (error.stack) {
|
|
109
|
+
sanitized.stack = sanitizeForLog(error.stack);
|
|
110
|
+
}
|
|
111
|
+
sanitized.name = error.name;
|
|
112
|
+
return sanitized;
|
|
113
|
+
}
|
|
114
|
+
/** Original console methods (saved before wrapping) */
|
|
115
|
+
let _originalConsoleLog = null;
|
|
116
|
+
let _originalConsoleError = null;
|
|
117
|
+
let _originalConsoleWarn = null;
|
|
118
|
+
let _originalConsoleDebug = null;
|
|
119
|
+
/** Whether console is currently wrapped */
|
|
120
|
+
let _isWrapped = false;
|
|
121
|
+
/**
|
|
122
|
+
* Wrap all console output methods through the sanitizer.
|
|
123
|
+
*
|
|
124
|
+
* After calling this function, all `console.log`, `console.error`,
|
|
125
|
+
* `console.warn`, and `console.debug` calls will have their arguments
|
|
126
|
+
* sanitized before output.
|
|
127
|
+
*
|
|
128
|
+
* Call `unwrapConsole()` to restore original behaviour.
|
|
129
|
+
*
|
|
130
|
+
* **Security:** This ensures that even if a developer accidentally
|
|
131
|
+
* logs a secret value, it will be redacted before reaching the terminal.
|
|
132
|
+
*/
|
|
133
|
+
export function wrapConsole() {
|
|
134
|
+
if (_isWrapped) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
_originalConsoleLog = console.log;
|
|
138
|
+
_originalConsoleError = console.error;
|
|
139
|
+
_originalConsoleWarn = console.warn;
|
|
140
|
+
_originalConsoleDebug = console.debug;
|
|
141
|
+
const wrapMethod = (original) => (...args) => {
|
|
142
|
+
const sanitizedArgs = args.map((arg) => {
|
|
143
|
+
if (typeof arg === 'string') {
|
|
144
|
+
return sanitizeForLog(arg);
|
|
145
|
+
}
|
|
146
|
+
if (arg instanceof Error) {
|
|
147
|
+
return sanitizeError(arg);
|
|
148
|
+
}
|
|
149
|
+
// For objects, sanitize JSON representation then parse back
|
|
150
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
151
|
+
try {
|
|
152
|
+
const json = JSON.stringify(arg);
|
|
153
|
+
const sanitized = sanitizeForLog(json);
|
|
154
|
+
return JSON.parse(sanitized);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// If JSON serialisation fails, return as-is
|
|
158
|
+
return arg;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return arg;
|
|
162
|
+
});
|
|
163
|
+
original.apply(console, sanitizedArgs);
|
|
164
|
+
};
|
|
165
|
+
console.log = wrapMethod(_originalConsoleLog);
|
|
166
|
+
console.error = wrapMethod(_originalConsoleError);
|
|
167
|
+
console.warn = wrapMethod(_originalConsoleWarn);
|
|
168
|
+
console.debug = wrapMethod(_originalConsoleDebug);
|
|
169
|
+
_isWrapped = true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Restore original console methods (unwrap the sanitizer).
|
|
173
|
+
*
|
|
174
|
+
* Safe to call even if `wrapConsole()` was never called.
|
|
175
|
+
*/
|
|
176
|
+
export function unwrapConsole() {
|
|
177
|
+
if (!_isWrapped) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (_originalConsoleLog)
|
|
181
|
+
console.log = _originalConsoleLog;
|
|
182
|
+
if (_originalConsoleError)
|
|
183
|
+
console.error = _originalConsoleError;
|
|
184
|
+
if (_originalConsoleWarn)
|
|
185
|
+
console.warn = _originalConsoleWarn;
|
|
186
|
+
if (_originalConsoleDebug)
|
|
187
|
+
console.debug = _originalConsoleDebug;
|
|
188
|
+
_originalConsoleLog = null;
|
|
189
|
+
_originalConsoleError = null;
|
|
190
|
+
_originalConsoleWarn = null;
|
|
191
|
+
_originalConsoleDebug = null;
|
|
192
|
+
_isWrapped = false;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check whether the console is currently wrapped by the sanitizer.
|
|
196
|
+
*
|
|
197
|
+
* @returns `true` if `wrapConsole()` has been called and `unwrapConsole()` has not.
|
|
198
|
+
*/
|
|
199
|
+
export function isConsoleWrapped() {
|
|
200
|
+
return _isWrapped;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=log-sanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-sanitizer.js","sourceRoot":"","sources":["../../src/core/log-sanitizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation module.
|
|
3
|
+
*
|
|
4
|
+
* Validates and canonicalises project paths to prevent path traversal
|
|
5
|
+
* attacks and restrict operations to safe directories (within $HOME
|
|
6
|
+
* or explicitly approved locations).
|
|
7
|
+
*
|
|
8
|
+
* @module core/path-validator
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Canonicalise a path string.
|
|
12
|
+
*
|
|
13
|
+
* Resolves `~` to the home directory, normalises `.` and `..` segments,
|
|
14
|
+
* and returns an absolute path. Does NOT follow symlinks — use
|
|
15
|
+
* `fs.realpathSync` separately if you need the real path.
|
|
16
|
+
*
|
|
17
|
+
* @param p - The path to canonicalise (may contain `~`).
|
|
18
|
+
* @returns The resolved absolute path.
|
|
19
|
+
*/
|
|
20
|
+
export declare function canonicalize(p: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Validate that a project path is safe to use.
|
|
23
|
+
*
|
|
24
|
+
* A path is valid if ALL of the following are true:
|
|
25
|
+
* - It is non-empty and resolves to an absolute path.
|
|
26
|
+
* - After canonicalisation, it is within the user's home directory.
|
|
27
|
+
* - It does not land inside a blocked system directory.
|
|
28
|
+
* - If the path exists and is a symlink, the symlink target is also
|
|
29
|
+
* within the home directory.
|
|
30
|
+
*
|
|
31
|
+
* @param p - The path to validate (may contain `~`).
|
|
32
|
+
* @returns The canonicalised, validated absolute path.
|
|
33
|
+
* @throws If the path is outside the home directory, in a blocked
|
|
34
|
+
* directory, or a symlink pointing outside the home directory.
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateProjectPath(p: string): string;
|
|
37
|
+
//# sourceMappingURL=path-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-validator.d.ts","sourceRoot":"","sources":["../../src/core/path-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyBH;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAmB9C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAqErD"}
|