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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync audit` command.
|
|
3
|
+
*
|
|
4
|
+
* Runs a comprehensive security audit:
|
|
5
|
+
* - Key file permissions (0o600).
|
|
6
|
+
* - Config directory permissions (0o700).
|
|
7
|
+
* - Remote transport security (SSH/HTTPS only).
|
|
8
|
+
* - All state files are .age (no plaintext .json state).
|
|
9
|
+
* - Git history scan for plaintext secret patterns.
|
|
10
|
+
* - Repo size report.
|
|
11
|
+
*
|
|
12
|
+
* Reports issues with severity levels: critical, warning, info.
|
|
13
|
+
*
|
|
14
|
+
* @module commands/audit
|
|
15
|
+
*/
|
|
16
|
+
import type { Command } from 'commander';
|
|
17
|
+
/** Severity level for audit findings */
|
|
18
|
+
export type AuditSeverity = 'critical' | 'warning' | 'info';
|
|
19
|
+
/** A single audit finding */
|
|
20
|
+
export interface AuditFinding {
|
|
21
|
+
severity: AuditSeverity;
|
|
22
|
+
check: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
/** Result of the full audit */
|
|
26
|
+
export interface AuditResult {
|
|
27
|
+
findings: AuditFinding[];
|
|
28
|
+
passed: boolean;
|
|
29
|
+
repoSizeBytes: number | null;
|
|
30
|
+
repoSizeHuman: string | null;
|
|
31
|
+
stateFileCount: number;
|
|
32
|
+
hasRemote: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check key file and config directory permissions.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkPermissions(configDir: string): AuditFinding[];
|
|
38
|
+
/**
|
|
39
|
+
* Validate remote transport security.
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkRemoteTransport(syncDir: string): {
|
|
42
|
+
findings: AuditFinding[];
|
|
43
|
+
hasRemote: boolean;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Verify all state files are .age (not .json).
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkStateFiles(syncDir: string): {
|
|
49
|
+
findings: AuditFinding[];
|
|
50
|
+
stateFileCount: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Scan Git history for plaintext secret patterns.
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkGitHistory(syncDir: string): AuditFinding[];
|
|
56
|
+
/**
|
|
57
|
+
* Report repository size.
|
|
58
|
+
*/
|
|
59
|
+
export declare function checkRepoSize(syncDir: string): {
|
|
60
|
+
findings: AuditFinding[];
|
|
61
|
+
sizeBytes: number | null;
|
|
62
|
+
sizeHuman: string | null;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Format bytes into human-readable string.
|
|
66
|
+
*/
|
|
67
|
+
export declare function formatBytes(bytes: number): string;
|
|
68
|
+
/**
|
|
69
|
+
* Execute the full audit.
|
|
70
|
+
*/
|
|
71
|
+
export declare function executeAudit(): AuditResult;
|
|
72
|
+
/**
|
|
73
|
+
* Register the `ctx-sync audit` command on the given program.
|
|
74
|
+
*/
|
|
75
|
+
export declare function registerAuditCommand(program: Command): void;
|
|
76
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,wCAAwC;AACxC,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,6BAA6B;AAC7B,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,+BAA+B;AAC/B,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;AAiBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAqBlE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG;IACrD,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;CACpB,CAmEA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG;IAChD,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB,CAuEA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CA6C/D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAC9C,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAsBA;AAqBD;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASjD;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,WAAW,CAkC1C;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0D3D"}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync audit` command.
|
|
3
|
+
*
|
|
4
|
+
* Runs a comprehensive security audit:
|
|
5
|
+
* - Key file permissions (0o600).
|
|
6
|
+
* - Config directory permissions (0o700).
|
|
7
|
+
* - Remote transport security (SSH/HTTPS only).
|
|
8
|
+
* - All state files are .age (no plaintext .json state).
|
|
9
|
+
* - Git history scan for plaintext secret patterns.
|
|
10
|
+
* - Repo size report.
|
|
11
|
+
*
|
|
12
|
+
* Reports issues with severity levels: critical, warning, info.
|
|
13
|
+
*
|
|
14
|
+
* @module commands/audit
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import { execSync } from 'node:child_process';
|
|
19
|
+
import { withErrorHandler } from '../utils/errors.js';
|
|
20
|
+
import { STATE_FILES } from '@ctx-sync/shared';
|
|
21
|
+
import { verifyPermissions } from '../core/key-store.js';
|
|
22
|
+
import { validateRemoteUrl } from '../core/transport.js';
|
|
23
|
+
import { getConfigDir, getSyncDir } from './init.js';
|
|
24
|
+
// ─── Audit Checks ─────────────────────────────────────────────────────────
|
|
25
|
+
/** Secret patterns to scan for in Git history */
|
|
26
|
+
const SECRET_PATTERNS = [
|
|
27
|
+
{ pattern: /sk_live_[a-zA-Z0-9]+/, label: 'Stripe live key' },
|
|
28
|
+
{ pattern: /sk_test_[a-zA-Z0-9]+/, label: 'Stripe test key' },
|
|
29
|
+
{ pattern: /ghp_[a-zA-Z0-9]+/, label: 'GitHub PAT' },
|
|
30
|
+
{ pattern: /gho_[a-zA-Z0-9]+/, label: 'GitHub OAuth token' },
|
|
31
|
+
{ pattern: /github_pat_[a-zA-Z0-9]+/, label: 'GitHub fine-grained PAT' },
|
|
32
|
+
{ pattern: /xoxb-[0-9]+-[0-9]+-[a-zA-Z0-9]+/, label: 'Slack bot token' },
|
|
33
|
+
{ pattern: /xoxp-[0-9]+-[0-9]+-[a-zA-Z0-9]+/, label: 'Slack user token' },
|
|
34
|
+
{ pattern: /AKIA[A-Z0-9]{16}/, label: 'AWS access key' },
|
|
35
|
+
{ pattern: /AGE-SECRET-KEY-[A-Z0-9]+/, label: 'Age private key' },
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Check key file and config directory permissions.
|
|
39
|
+
*/
|
|
40
|
+
export function checkPermissions(configDir) {
|
|
41
|
+
const findings = [];
|
|
42
|
+
const result = verifyPermissions(configDir);
|
|
43
|
+
if (result.valid) {
|
|
44
|
+
findings.push({
|
|
45
|
+
severity: 'info',
|
|
46
|
+
check: 'permissions',
|
|
47
|
+
message: 'Key file (600) and config directory (700) permissions are correct.',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
for (const issue of result.issues) {
|
|
52
|
+
findings.push({
|
|
53
|
+
severity: 'critical',
|
|
54
|
+
check: 'permissions',
|
|
55
|
+
message: issue,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return findings;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate remote transport security.
|
|
63
|
+
*/
|
|
64
|
+
export function checkRemoteTransport(syncDir) {
|
|
65
|
+
const findings = [];
|
|
66
|
+
let hasRemote = false;
|
|
67
|
+
const gitDir = path.join(syncDir, '.git');
|
|
68
|
+
if (!fs.existsSync(gitDir)) {
|
|
69
|
+
findings.push({
|
|
70
|
+
severity: 'warning',
|
|
71
|
+
check: 'transport',
|
|
72
|
+
message: 'No Git repository found in sync directory.',
|
|
73
|
+
});
|
|
74
|
+
return { findings, hasRemote };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const remoteOutput = execSync('git remote -v', {
|
|
78
|
+
cwd: syncDir,
|
|
79
|
+
encoding: 'utf-8',
|
|
80
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
81
|
+
}).trim();
|
|
82
|
+
if (!remoteOutput) {
|
|
83
|
+
findings.push({
|
|
84
|
+
severity: 'info',
|
|
85
|
+
check: 'transport',
|
|
86
|
+
message: 'No remote configured (local-only mode).',
|
|
87
|
+
});
|
|
88
|
+
return { findings, hasRemote };
|
|
89
|
+
}
|
|
90
|
+
hasRemote = true;
|
|
91
|
+
// Parse remote URLs
|
|
92
|
+
const lines = remoteOutput.split('\n');
|
|
93
|
+
const urls = new Set();
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const parts = line.split(/\s+/);
|
|
96
|
+
if (parts[1]) {
|
|
97
|
+
urls.add(parts[1]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const url of urls) {
|
|
101
|
+
try {
|
|
102
|
+
validateRemoteUrl(url);
|
|
103
|
+
findings.push({
|
|
104
|
+
severity: 'info',
|
|
105
|
+
check: 'transport',
|
|
106
|
+
message: `Remote URL is secure: ${url}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
findings.push({
|
|
111
|
+
severity: 'critical',
|
|
112
|
+
check: 'transport',
|
|
113
|
+
message: `Insecure remote: ${err instanceof Error ? err.message : String(err)}`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
findings.push({
|
|
120
|
+
severity: 'warning',
|
|
121
|
+
check: 'transport',
|
|
122
|
+
message: 'Could not check remote URLs (git command failed).',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return { findings, hasRemote };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Verify all state files are .age (not .json).
|
|
129
|
+
*/
|
|
130
|
+
export function checkStateFiles(syncDir) {
|
|
131
|
+
const findings = [];
|
|
132
|
+
if (!fs.existsSync(syncDir)) {
|
|
133
|
+
findings.push({
|
|
134
|
+
severity: 'warning',
|
|
135
|
+
check: 'state-files',
|
|
136
|
+
message: 'Sync directory does not exist.',
|
|
137
|
+
});
|
|
138
|
+
return { findings, stateFileCount: 0 };
|
|
139
|
+
}
|
|
140
|
+
const entries = fs.readdirSync(syncDir).filter((e) => !e.startsWith('.') && e !== 'sessions');
|
|
141
|
+
const ageFiles = entries.filter((e) => e.endsWith('.age'));
|
|
142
|
+
const jsonFiles = entries.filter((e) => e.endsWith('.json') && e !== STATE_FILES.MANIFEST);
|
|
143
|
+
if (jsonFiles.length > 0) {
|
|
144
|
+
for (const file of jsonFiles) {
|
|
145
|
+
findings.push({
|
|
146
|
+
severity: 'critical',
|
|
147
|
+
check: 'state-files',
|
|
148
|
+
message: `Plaintext state file found: ${file}. State files must be encrypted as .age files.`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (ageFiles.length > 0) {
|
|
153
|
+
findings.push({
|
|
154
|
+
severity: 'info',
|
|
155
|
+
check: 'state-files',
|
|
156
|
+
message: `${String(ageFiles.length)} encrypted state file(s) found.`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Check manifest is present and minimal
|
|
160
|
+
const manifestPath = path.join(syncDir, STATE_FILES.MANIFEST);
|
|
161
|
+
if (fs.existsSync(manifestPath)) {
|
|
162
|
+
try {
|
|
163
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
164
|
+
const allowedKeys = ['version', 'lastSync', 'files'];
|
|
165
|
+
const extraKeys = Object.keys(manifest).filter((k) => !allowedKeys.includes(k));
|
|
166
|
+
if (extraKeys.length > 0) {
|
|
167
|
+
findings.push({
|
|
168
|
+
severity: 'warning',
|
|
169
|
+
check: 'state-files',
|
|
170
|
+
message: `Manifest contains unexpected keys: ${extraKeys.join(', ')}`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
findings.push({
|
|
175
|
+
severity: 'info',
|
|
176
|
+
check: 'state-files',
|
|
177
|
+
message: 'Manifest contains only version and timestamps (correct).',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
findings.push({
|
|
183
|
+
severity: 'warning',
|
|
184
|
+
check: 'state-files',
|
|
185
|
+
message: 'Could not parse manifest.json.',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { findings, stateFileCount: ageFiles.length };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Scan Git history for plaintext secret patterns.
|
|
193
|
+
*/
|
|
194
|
+
export function checkGitHistory(syncDir) {
|
|
195
|
+
const findings = [];
|
|
196
|
+
const gitDir = path.join(syncDir, '.git');
|
|
197
|
+
if (!fs.existsSync(gitDir)) {
|
|
198
|
+
return findings; // No git repo, nothing to scan
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const history = execSync('git log -p --all --full-history', {
|
|
202
|
+
cwd: syncDir,
|
|
203
|
+
encoding: 'utf-8',
|
|
204
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
205
|
+
maxBuffer: 50 * 1024 * 1024, // 50MB
|
|
206
|
+
});
|
|
207
|
+
let foundSecrets = false;
|
|
208
|
+
for (const { pattern, label } of SECRET_PATTERNS) {
|
|
209
|
+
if (pattern.test(history)) {
|
|
210
|
+
foundSecrets = true;
|
|
211
|
+
findings.push({
|
|
212
|
+
severity: 'critical',
|
|
213
|
+
check: 'git-history',
|
|
214
|
+
message: `Potential ${label} found in Git history. Run \`key rotate\` to re-encrypt and rewrite history.`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!foundSecrets) {
|
|
219
|
+
findings.push({
|
|
220
|
+
severity: 'info',
|
|
221
|
+
check: 'git-history',
|
|
222
|
+
message: 'No plaintext secret patterns found in Git history.',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// May fail if there's no commit history yet
|
|
228
|
+
findings.push({
|
|
229
|
+
severity: 'info',
|
|
230
|
+
check: 'git-history',
|
|
231
|
+
message: 'No Git history to scan (no commits yet).',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return findings;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Report repository size.
|
|
238
|
+
*/
|
|
239
|
+
export function checkRepoSize(syncDir) {
|
|
240
|
+
const findings = [];
|
|
241
|
+
if (!fs.existsSync(syncDir)) {
|
|
242
|
+
return { findings, sizeBytes: null, sizeHuman: null };
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
// Calculate total size recursively
|
|
246
|
+
const sizeBytes = getDirectorySize(syncDir);
|
|
247
|
+
const sizeHuman = formatBytes(sizeBytes);
|
|
248
|
+
findings.push({
|
|
249
|
+
severity: sizeBytes > 100 * 1024 * 1024 ? 'warning' : 'info',
|
|
250
|
+
check: 'repo-size',
|
|
251
|
+
message: `Repository size: ${sizeHuman}${sizeBytes > 100 * 1024 * 1024 ? ' (consider cleanup)' : ''}`,
|
|
252
|
+
});
|
|
253
|
+
return { findings, sizeBytes, sizeHuman };
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return { findings, sizeBytes: null, sizeHuman: null };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Calculate directory size recursively.
|
|
261
|
+
*/
|
|
262
|
+
function getDirectorySize(dirPath) {
|
|
263
|
+
let totalSize = 0;
|
|
264
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
265
|
+
for (const entry of entries) {
|
|
266
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
267
|
+
if (entry.isDirectory()) {
|
|
268
|
+
totalSize += getDirectorySize(fullPath);
|
|
269
|
+
}
|
|
270
|
+
else if (entry.isFile()) {
|
|
271
|
+
totalSize += fs.statSync(fullPath).size;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return totalSize;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Format bytes into human-readable string.
|
|
278
|
+
*/
|
|
279
|
+
export function formatBytes(bytes) {
|
|
280
|
+
if (bytes === 0)
|
|
281
|
+
return '0 B';
|
|
282
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
283
|
+
const k = 1024;
|
|
284
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
285
|
+
const value = bytes / Math.pow(k, i);
|
|
286
|
+
return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i] ?? 'TB'}`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Execute the full audit.
|
|
290
|
+
*/
|
|
291
|
+
export function executeAudit() {
|
|
292
|
+
const configDir = getConfigDir();
|
|
293
|
+
const syncDir = getSyncDir();
|
|
294
|
+
const allFindings = [];
|
|
295
|
+
// 1. Permissions check
|
|
296
|
+
allFindings.push(...checkPermissions(configDir));
|
|
297
|
+
// 2. Remote transport check
|
|
298
|
+
const transport = checkRemoteTransport(syncDir);
|
|
299
|
+
allFindings.push(...transport.findings);
|
|
300
|
+
// 3. State files check
|
|
301
|
+
const stateFiles = checkStateFiles(syncDir);
|
|
302
|
+
allFindings.push(...stateFiles.findings);
|
|
303
|
+
// 4. Git history scan
|
|
304
|
+
allFindings.push(...checkGitHistory(syncDir));
|
|
305
|
+
// 5. Repo size
|
|
306
|
+
const repoSize = checkRepoSize(syncDir);
|
|
307
|
+
allFindings.push(...repoSize.findings);
|
|
308
|
+
const hasCritical = allFindings.some((f) => f.severity === 'critical');
|
|
309
|
+
return {
|
|
310
|
+
findings: allFindings,
|
|
311
|
+
passed: !hasCritical,
|
|
312
|
+
repoSizeBytes: repoSize.sizeBytes,
|
|
313
|
+
repoSizeHuman: repoSize.sizeHuman,
|
|
314
|
+
stateFileCount: stateFiles.stateFileCount,
|
|
315
|
+
hasRemote: transport.hasRemote,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
// ─── Commander Registration ───────────────────────────────────────────────
|
|
319
|
+
/**
|
|
320
|
+
* Register the `ctx-sync audit` command on the given program.
|
|
321
|
+
*/
|
|
322
|
+
export function registerAuditCommand(program) {
|
|
323
|
+
program
|
|
324
|
+
.command('audit')
|
|
325
|
+
.description('Run a comprehensive security audit')
|
|
326
|
+
.action(withErrorHandler(async () => {
|
|
327
|
+
const result = executeAudit();
|
|
328
|
+
const critical = result.findings.filter((f) => f.severity === 'critical');
|
|
329
|
+
const warnings = result.findings.filter((f) => f.severity === 'warning');
|
|
330
|
+
const info = result.findings.filter((f) => f.severity === 'info');
|
|
331
|
+
console.log('\n🔒 ctx-sync Security Audit\n');
|
|
332
|
+
if (critical.length > 0) {
|
|
333
|
+
console.log('❌ Critical Issues:');
|
|
334
|
+
for (const f of critical) {
|
|
335
|
+
console.log(` [${f.check}] ${f.message}`);
|
|
336
|
+
}
|
|
337
|
+
console.log('');
|
|
338
|
+
}
|
|
339
|
+
if (warnings.length > 0) {
|
|
340
|
+
console.log('⚠ Warnings:');
|
|
341
|
+
for (const f of warnings) {
|
|
342
|
+
console.log(` [${f.check}] ${f.message}`);
|
|
343
|
+
}
|
|
344
|
+
console.log('');
|
|
345
|
+
}
|
|
346
|
+
if (info.length > 0) {
|
|
347
|
+
console.log('ℹ Info:');
|
|
348
|
+
for (const f of info) {
|
|
349
|
+
console.log(` [${f.check}] ${f.message}`);
|
|
350
|
+
}
|
|
351
|
+
console.log('');
|
|
352
|
+
}
|
|
353
|
+
if (result.repoSizeHuman) {
|
|
354
|
+
console.log(`📦 Repository size: ${result.repoSizeHuman}`);
|
|
355
|
+
}
|
|
356
|
+
console.log(`📄 Encrypted state files: ${String(result.stateFileCount)}`);
|
|
357
|
+
console.log('');
|
|
358
|
+
if (result.passed) {
|
|
359
|
+
console.log('✅ Audit passed — no critical issues found.');
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
console.log('❌ Audit failed — critical issues require attention.');
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync config` command group.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* - `config safe-list` — View the current safe-list (default + custom).
|
|
6
|
+
* - `config safe-list add <key>` — Add a key to the user's custom safe-list.
|
|
7
|
+
* - `config safe-list remove <key>` — Remove a key from the custom safe-list.
|
|
8
|
+
*
|
|
9
|
+
* The safe-list determines which env var keys MAY be stored as plaintext
|
|
10
|
+
* when `--allow-plain` is used during `env import`. Everything else is
|
|
11
|
+
* encrypted by default.
|
|
12
|
+
*
|
|
13
|
+
* Config is stored in `~/.config/ctx-sync/config.json` (local, never synced).
|
|
14
|
+
*
|
|
15
|
+
* @module commands/config
|
|
16
|
+
*/
|
|
17
|
+
import type { Command } from 'commander';
|
|
18
|
+
/**
|
|
19
|
+
* Result of listing the safe-list.
|
|
20
|
+
*/
|
|
21
|
+
export interface SafeListViewResult {
|
|
22
|
+
defaults: readonly string[];
|
|
23
|
+
custom: string[];
|
|
24
|
+
effective: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Execute safe-list view.
|
|
28
|
+
*
|
|
29
|
+
* @returns The safe-list broken down by defaults, custom, and effective.
|
|
30
|
+
*/
|
|
31
|
+
export declare function executeSafeListView(): SafeListViewResult;
|
|
32
|
+
/**
|
|
33
|
+
* Execute safe-list add.
|
|
34
|
+
*
|
|
35
|
+
* @param key - The env var key to add to the safe-list.
|
|
36
|
+
* @returns Object with `added` flag and descriptive message.
|
|
37
|
+
*/
|
|
38
|
+
export declare function executeSafeListAdd(key: string): {
|
|
39
|
+
added: boolean;
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Execute safe-list remove.
|
|
44
|
+
*
|
|
45
|
+
* @param key - The env var key to remove from the safe-list.
|
|
46
|
+
* @returns Object with `removed` flag and descriptive message.
|
|
47
|
+
*/
|
|
48
|
+
export declare function executeSafeListRemove(key: string): {
|
|
49
|
+
removed: boolean;
|
|
50
|
+
message: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Register the `config` command group on the given Commander program.
|
|
54
|
+
*
|
|
55
|
+
* @param program - The Commander program instance.
|
|
56
|
+
*/
|
|
57
|
+
export declare function registerConfigCommand(program: Command): void;
|
|
58
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,kBAAkB,CAGxD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG;IAC/C,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAGA;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAGA;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiF5D"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync config` command group.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* - `config safe-list` — View the current safe-list (default + custom).
|
|
6
|
+
* - `config safe-list add <key>` — Add a key to the user's custom safe-list.
|
|
7
|
+
* - `config safe-list remove <key>` — Remove a key from the custom safe-list.
|
|
8
|
+
*
|
|
9
|
+
* The safe-list determines which env var keys MAY be stored as plaintext
|
|
10
|
+
* when `--allow-plain` is used during `env import`. Everything else is
|
|
11
|
+
* encrypted by default.
|
|
12
|
+
*
|
|
13
|
+
* Config is stored in `~/.config/ctx-sync/config.json` (local, never synced).
|
|
14
|
+
*
|
|
15
|
+
* @module commands/config
|
|
16
|
+
*/
|
|
17
|
+
import { withErrorHandler } from '../utils/errors.js';
|
|
18
|
+
import { getConfigDir } from './init.js';
|
|
19
|
+
import { listSafeList, addToSafeList, removeFromSafeList, } from '../core/config-store.js';
|
|
20
|
+
/**
|
|
21
|
+
* Execute safe-list view.
|
|
22
|
+
*
|
|
23
|
+
* @returns The safe-list broken down by defaults, custom, and effective.
|
|
24
|
+
*/
|
|
25
|
+
export function executeSafeListView() {
|
|
26
|
+
const configDir = getConfigDir();
|
|
27
|
+
return listSafeList(configDir);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Execute safe-list add.
|
|
31
|
+
*
|
|
32
|
+
* @param key - The env var key to add to the safe-list.
|
|
33
|
+
* @returns Object with `added` flag and descriptive message.
|
|
34
|
+
*/
|
|
35
|
+
export function executeSafeListAdd(key) {
|
|
36
|
+
const configDir = getConfigDir();
|
|
37
|
+
return addToSafeList(configDir, key);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Execute safe-list remove.
|
|
41
|
+
*
|
|
42
|
+
* @param key - The env var key to remove from the safe-list.
|
|
43
|
+
* @returns Object with `removed` flag and descriptive message.
|
|
44
|
+
*/
|
|
45
|
+
export function executeSafeListRemove(key) {
|
|
46
|
+
const configDir = getConfigDir();
|
|
47
|
+
return removeFromSafeList(configDir, key);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register the `config` command group on the given Commander program.
|
|
51
|
+
*
|
|
52
|
+
* @param program - The Commander program instance.
|
|
53
|
+
*/
|
|
54
|
+
export function registerConfigCommand(program) {
|
|
55
|
+
const configCmd = program
|
|
56
|
+
.command('config')
|
|
57
|
+
.description('Manage local configuration and preferences');
|
|
58
|
+
// ── config safe-list ──────────────────────────────────────────────
|
|
59
|
+
const safeListCmd = configCmd
|
|
60
|
+
.command('safe-list')
|
|
61
|
+
.description('View or manage the env var safe-list');
|
|
62
|
+
// Default action: view the safe-list
|
|
63
|
+
safeListCmd.action(withErrorHandler(async () => {
|
|
64
|
+
const result = executeSafeListView();
|
|
65
|
+
const chalk = (await import('chalk')).default;
|
|
66
|
+
console.log(chalk.bold('\nEnvironment Variable Safe-List\n'));
|
|
67
|
+
console.log(chalk.dim('Keys on the safe-list MAY be stored as plaintext when --allow-plain is used.\n' +
|
|
68
|
+
'Everything else is always encrypted (encrypt-by-default).\n'));
|
|
69
|
+
console.log(chalk.cyan('Built-in defaults:'));
|
|
70
|
+
for (const key of result.defaults) {
|
|
71
|
+
console.log(` ${key}`);
|
|
72
|
+
}
|
|
73
|
+
if (result.custom.length > 0) {
|
|
74
|
+
console.log(chalk.cyan('\nCustom additions:'));
|
|
75
|
+
for (const key of result.custom) {
|
|
76
|
+
console.log(` ${key}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(chalk.dim('\nNo custom additions.'));
|
|
81
|
+
}
|
|
82
|
+
console.log(chalk.dim(`\nTotal effective safe-list: ${result.effective.length} keys`));
|
|
83
|
+
}));
|
|
84
|
+
// ── config safe-list add <key> ────────────────────────────────────
|
|
85
|
+
safeListCmd
|
|
86
|
+
.command('add <key>')
|
|
87
|
+
.description('Add a key to your custom safe-list')
|
|
88
|
+
.action(withErrorHandler(async (key) => {
|
|
89
|
+
const result = executeSafeListAdd(key);
|
|
90
|
+
const chalk = (await import('chalk')).default;
|
|
91
|
+
if (result.added) {
|
|
92
|
+
console.log(chalk.green(`✅ ${result.message}`));
|
|
93
|
+
console.log(chalk.dim(' This key will be treated as plaintext-safe when --allow-plain is used.'));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(chalk.yellow(`⚠️ ${result.message}`));
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
// ── config safe-list remove <key> ─────────────────────────────────
|
|
100
|
+
safeListCmd
|
|
101
|
+
.command('remove <key>')
|
|
102
|
+
.description('Remove a key from your custom safe-list')
|
|
103
|
+
.action(withErrorHandler(async (key) => {
|
|
104
|
+
const result = executeSafeListRemove(key);
|
|
105
|
+
const chalk = (await import('chalk')).default;
|
|
106
|
+
if (result.removed) {
|
|
107
|
+
console.log(chalk.green(`✅ ${result.message}`));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(chalk.yellow(`⚠️ ${result.message}`));
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AAWjC;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAI5C,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,aAAa,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAI/C,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,SAAS,GAAG,OAAO;SACtB,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,4CAA4C,CAAC,CAAC;IAE7D,qEAAqE;IACrE,MAAM,WAAW,GAAG,SAAS;SAC1B,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,sCAAsC,CAAC,CAAC;IAEvD,qCAAqC;IACrC,WAAW,CAAC,MAAM,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;QAC7C,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAC;QAErC,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAE9C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CACP,gFAAgF;YAC9E,6DAA6D,CAChE,CACF,CAAC;QAEF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC;YAC/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC;QACnD,CAAC;QAED,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CACP,gCAAgC,MAAM,CAAC,SAAS,CAAC,MAAM,OAAO,CAC/D,CACF,CAAC;IACJ,CAAC,CAAC,CAAC,CAAC;IAEJ,qEAAqE;IACrE,WAAW;SACR,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,oCAAoC,CAAC;SACjD,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAEvC,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAE9C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CACP,2EAA2E,CAC5E,CACF,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;IAEN,qEAAqE;IACrE,WAAW;SACR,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,yCAAyC,CAAC;SACtD,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAE1C,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAE9C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;AACR,CAAC"}
|