@s-gw/s-gw 0.1.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/.codex-plugin/plugin.json +35 -0
- package/.mcp.json +16 -0
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +197 -0
- package/TRADEMARKS.md +9 -0
- package/assets/icons/aws-ec2.png +0 -0
- package/assets/icons/lucide/bot.svg +8 -0
- package/assets/icons/lucide/monitor.svg +5 -0
- package/assets/icons/lucide/server.svg +6 -0
- package/assets/icons/lucide/terminal.svg +4 -0
- package/assets/icons/s-gw-128.png +0 -0
- package/assets/icons/s-gw-16.png +0 -0
- package/assets/icons/s-gw-180.png +0 -0
- package/assets/icons/s-gw-192.png +0 -0
- package/assets/icons/s-gw-32.png +0 -0
- package/assets/icons/s-gw-64.png +0 -0
- package/assets/icons/s-gw-menu-bar-template.png +0 -0
- package/dist/agent-context.d.ts +17 -0
- package/dist/agent-context.js +207 -0
- package/dist/agents.d.ts +64 -0
- package/dist/agents.js +763 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1385 -0
- package/dist/command-suggest.d.ts +3 -0
- package/dist/command-suggest.js +131 -0
- package/dist/console-server.d.ts +16 -0
- package/dist/console-server.js +978 -0
- package/dist/console-ui/assets/codex-DYTPdPxi.png +0 -0
- package/dist/console-ui/assets/cursor-CBrUTJD-.png +0 -0
- package/dist/console-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/console-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/console-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/console-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/console-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/console-ui/assets/hermes-B8hNbJPm.png +0 -0
- package/dist/console-ui/assets/index-BxUf0Sye.js +96 -0
- package/dist/console-ui/assets/index-CmTiBR_w.css +2 -0
- package/dist/console-ui/assets/omnigent-Cxa4p2Mq.png +0 -0
- package/dist/console-ui/assets/openclaw-C5wL4ZVW.png +0 -0
- package/dist/console-ui/assets/opencode-D_wFATSC.png +0 -0
- package/dist/console-ui/assets/openhands-DnrlGgev.svg +9 -0
- package/dist/console-ui/assets/s-gw-64-ByMUGQ3K.png +0 -0
- package/dist/console-ui/assets/vscode-Bdtr9eyf.png +0 -0
- package/dist/console-ui/assets/zeptoclaw-DztQW8Sw.png +0 -0
- package/dist/console-ui/index.html +13 -0
- package/dist/crypto.d.ts +6 -0
- package/dist/crypto.js +53 -0
- package/dist/executor.d.ts +7 -0
- package/dist/executor.js +297 -0
- package/dist/gateway.d.ts +31 -0
- package/dist/gateway.js +114 -0
- package/dist/guard.d.ts +61 -0
- package/dist/guard.js +247 -0
- package/dist/install.d.ts +146 -0
- package/dist/install.js +629 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +119 -0
- package/dist/native/s-gw-core +0 -0
- package/dist/native/s-gw-keychain-helper +0 -0
- package/dist/onepassword.d.ts +48 -0
- package/dist/onepassword.js +412 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +22 -0
- package/dist/s-gw Menu Bar.app/Contents/Info.plist +28 -0
- package/dist/s-gw Menu Bar.app/Contents/MacOS/s-gw-menu-bar-helper +0 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/AwsEc2.png +0 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-bot.svg +8 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-monitor.svg +5 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-server.svg +6 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-terminal.svg +4 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/MenuBarTemplate.png +0 -0
- package/dist/s-gw Menu Bar.app/Contents/_CodeSignature/CodeResources +194 -0
- package/dist/s-gw.app/Contents/Info.plist +28 -0
- package/dist/s-gw.app/Contents/MacOS/s-gw +0 -0
- package/dist/s-gw.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/s-gw.app/Contents/Resources/MenuBarTemplate.png +0 -0
- package/dist/s-gw.app/Contents/_CodeSignature/CodeResources +139 -0
- package/dist/scanner.d.ts +9 -0
- package/dist/scanner.js +437 -0
- package/dist/ssh.d.ts +31 -0
- package/dist/ssh.js +286 -0
- package/dist/store.d.ts +131 -0
- package/dist/store.js +1611 -0
- package/dist/types.d.ts +196 -0
- package/dist/types.js +2 -0
- package/dist/unlock.d.ts +29 -0
- package/dist/unlock.js +274 -0
- package/dist/windows/VERSION.txt +1 -0
- package/dist/windows/s-gw-client.cmd +4 -0
- package/dist/windows/s-gw-client.ps1 +106 -0
- package/dist/windows/s-gw-credential.cmd +4 -0
- package/dist/windows/s-gw-credential.ps1 +167 -0
- package/dist/windows/s-gw-helper.cmd +4 -0
- package/dist/windows/s-gw-helper.ps1 +180 -0
- package/docs/README.md +23 -0
- package/docs/agents.md +160 -0
- package/docs/architecture.md +72 -0
- package/docs/deployment.md +447 -0
- package/docs/detection.md +44 -0
- package/docs/images/s-gw-overview.png +0 -0
- package/docs/integrations.md +195 -0
- package/docs/keychain.md +39 -0
- package/docs/onepassword.md +84 -0
- package/docs/quickstart.md +104 -0
- package/docs/threat-model.md +100 -0
- package/docs/ui/THIRD_PARTY_NOTICES.md +111 -0
- package/docs/ui/apple-touch-icon.png +0 -0
- package/docs/ui/favicon-32.png +0 -0
- package/docs/ui/local-console.html +4477 -0
- package/docs/ui/vendor/d3-sankey/d3-array.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-array.min.js +2 -0
- package/docs/ui/vendor/d3-sankey/d3-path.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-path.min.js +2 -0
- package/docs/ui/vendor/d3-sankey/d3-sankey.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-sankey.min.js +2 -0
- package/docs/ui/vendor/d3-sankey/d3-shape.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-shape.min.js +2 -0
- package/docs/ui/vendor/sankeymatic/LICENSE.txt +17 -0
- package/docs/ui/vendor/sankeymatic/sankey.js +897 -0
- package/package.json +117 -0
- package/skills/s-gw/SKILL.md +19 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export type SecretType = "api-token" | "ssh-key" | "private-key" | "password" | "credential" | "access-key" | "unknown";
|
|
2
|
+
export type SecretSeverity = "low" | "medium" | "high" | "critical";
|
|
3
|
+
export type RequestState = "pending" | "approved" | "executing" | "denied" | "executed" | "failed";
|
|
4
|
+
export type ApprovalMode = "per-transaction" | "timed-session" | "login-session" | "always";
|
|
5
|
+
export type ApprovalAgentScope = "same-agent" | "any-agent";
|
|
6
|
+
export type ApprovalPolicyDecision = "ask" | "allow" | "deny";
|
|
7
|
+
export type ApprovalPolicyActionKind = "env_command" | "ssh_session";
|
|
8
|
+
export type AgentIdentitySource = "configured" | "mcp-client" | "runtime" | "process" | "reason" | "manual" | "unknown";
|
|
9
|
+
export interface ApprovalSettings {
|
|
10
|
+
mode: ApprovalMode;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ApprovalGrant {
|
|
14
|
+
id: string;
|
|
15
|
+
handle: string;
|
|
16
|
+
actionKey: string;
|
|
17
|
+
mode: Exclude<ApprovalMode, "per-transaction">;
|
|
18
|
+
agentScope?: ApprovalAgentScope;
|
|
19
|
+
agentName?: string;
|
|
20
|
+
loginSessionId: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
expiresAt?: string;
|
|
24
|
+
lastRequestId?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ApprovalPolicyConditions {
|
|
27
|
+
handles?: string[];
|
|
28
|
+
secretTypes?: SecretType[];
|
|
29
|
+
providers?: string[];
|
|
30
|
+
minSeverity?: SecretSeverity;
|
|
31
|
+
agents?: string[];
|
|
32
|
+
actionKinds?: ApprovalPolicyActionKind[];
|
|
33
|
+
commands?: string[];
|
|
34
|
+
injectEnvs?: string[];
|
|
35
|
+
workingDirs?: string[];
|
|
36
|
+
sshTargets?: string[];
|
|
37
|
+
sshPorts?: number[];
|
|
38
|
+
}
|
|
39
|
+
export interface ApprovalPolicyRule {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
priority: number;
|
|
44
|
+
decision: ApprovalPolicyDecision;
|
|
45
|
+
conditions: ApprovalPolicyConditions;
|
|
46
|
+
expiresAt?: string;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
}
|
|
50
|
+
export interface EncryptedBox {
|
|
51
|
+
alg: "aes-256-gcm";
|
|
52
|
+
kdf: "scrypt";
|
|
53
|
+
salt: string;
|
|
54
|
+
iv: string;
|
|
55
|
+
authTag: string;
|
|
56
|
+
ciphertext: string;
|
|
57
|
+
}
|
|
58
|
+
export interface SecretPolicy {
|
|
59
|
+
injectEnv?: string;
|
|
60
|
+
allowedCommands: string[];
|
|
61
|
+
maxOutputBytes: number;
|
|
62
|
+
}
|
|
63
|
+
export type SecretBackend = "local" | "onepassword" | "keychain";
|
|
64
|
+
export interface SecretValueCache {
|
|
65
|
+
backend: "onepassword";
|
|
66
|
+
encrypted: EncryptedBox;
|
|
67
|
+
fingerprint: string;
|
|
68
|
+
approvalGrantId: string;
|
|
69
|
+
createdAt: string;
|
|
70
|
+
updatedAt: string;
|
|
71
|
+
expiresAt?: string;
|
|
72
|
+
loginSessionId?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SecretRecord {
|
|
75
|
+
handle: string;
|
|
76
|
+
name: string;
|
|
77
|
+
type: SecretType;
|
|
78
|
+
backend?: SecretBackend;
|
|
79
|
+
provider?: string;
|
|
80
|
+
ruleId?: string;
|
|
81
|
+
severity?: SecretSeverity;
|
|
82
|
+
confidence?: number;
|
|
83
|
+
createdAt: string;
|
|
84
|
+
updatedAt: string;
|
|
85
|
+
source?: string;
|
|
86
|
+
fingerprint: string;
|
|
87
|
+
encrypted: EncryptedBox;
|
|
88
|
+
cache?: SecretValueCache;
|
|
89
|
+
policy: SecretPolicy;
|
|
90
|
+
}
|
|
91
|
+
export interface HandleSummary {
|
|
92
|
+
handle: string;
|
|
93
|
+
name: string;
|
|
94
|
+
type: SecretType;
|
|
95
|
+
backend?: SecretBackend;
|
|
96
|
+
provider?: string;
|
|
97
|
+
ruleId?: string;
|
|
98
|
+
severity?: SecretSeverity;
|
|
99
|
+
confidence?: number;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
updatedAt: string;
|
|
102
|
+
source?: string;
|
|
103
|
+
fingerprint: string;
|
|
104
|
+
policy: SecretPolicy;
|
|
105
|
+
}
|
|
106
|
+
export interface SshSessionSpec {
|
|
107
|
+
target: string;
|
|
108
|
+
port: number;
|
|
109
|
+
}
|
|
110
|
+
export interface CommandEnvBinding {
|
|
111
|
+
handle: string;
|
|
112
|
+
injectEnv: string;
|
|
113
|
+
}
|
|
114
|
+
export interface CommandAction {
|
|
115
|
+
kind: "env_command" | "ssh_session";
|
|
116
|
+
command: string;
|
|
117
|
+
args: string[];
|
|
118
|
+
injectEnv: string;
|
|
119
|
+
env?: CommandEnvBinding[];
|
|
120
|
+
workingDir?: string;
|
|
121
|
+
timeoutMs: number;
|
|
122
|
+
ssh?: SshSessionSpec;
|
|
123
|
+
}
|
|
124
|
+
export interface ExecutionSummary {
|
|
125
|
+
exitCode: number | null;
|
|
126
|
+
signal: NodeJS.Signals | null;
|
|
127
|
+
stdout: string;
|
|
128
|
+
stderr: string;
|
|
129
|
+
proof: string;
|
|
130
|
+
durationMs: number;
|
|
131
|
+
timeoutMs: number;
|
|
132
|
+
timedOut: boolean;
|
|
133
|
+
sanitized: boolean;
|
|
134
|
+
}
|
|
135
|
+
export interface RequestRecord {
|
|
136
|
+
id: string;
|
|
137
|
+
handle: string;
|
|
138
|
+
reason: string;
|
|
139
|
+
agentName?: string;
|
|
140
|
+
agentSource?: AgentIdentitySource;
|
|
141
|
+
action: CommandAction;
|
|
142
|
+
state: RequestState;
|
|
143
|
+
createdAt: string;
|
|
144
|
+
updatedAt: string;
|
|
145
|
+
approvedAt?: string;
|
|
146
|
+
approvalGrantId?: string;
|
|
147
|
+
approvalPolicyRuleId?: string;
|
|
148
|
+
deniedAt?: string;
|
|
149
|
+
executedAt?: string;
|
|
150
|
+
resultSummary?: ExecutionSummary;
|
|
151
|
+
error?: string;
|
|
152
|
+
}
|
|
153
|
+
export interface AuditEvent {
|
|
154
|
+
id: string;
|
|
155
|
+
ts: string;
|
|
156
|
+
type: string;
|
|
157
|
+
handle?: string;
|
|
158
|
+
requestId?: string;
|
|
159
|
+
message: string;
|
|
160
|
+
}
|
|
161
|
+
export interface StoreFile {
|
|
162
|
+
version: 1;
|
|
163
|
+
secrets: SecretRecord[];
|
|
164
|
+
requests: RequestRecord[];
|
|
165
|
+
audit: AuditEvent[];
|
|
166
|
+
approvalSettings: ApprovalSettings;
|
|
167
|
+
approvalGrants: ApprovalGrant[];
|
|
168
|
+
approvalPolicyRules: ApprovalPolicyRule[];
|
|
169
|
+
}
|
|
170
|
+
export interface ScanCandidate {
|
|
171
|
+
type: SecretType;
|
|
172
|
+
label: string;
|
|
173
|
+
provider?: string;
|
|
174
|
+
ruleId?: string;
|
|
175
|
+
severity?: SecretSeverity;
|
|
176
|
+
confidence?: number;
|
|
177
|
+
value: string;
|
|
178
|
+
start: number;
|
|
179
|
+
end: number;
|
|
180
|
+
}
|
|
181
|
+
export interface ScanFinding {
|
|
182
|
+
type: SecretType;
|
|
183
|
+
label: string;
|
|
184
|
+
provider?: string;
|
|
185
|
+
ruleId?: string;
|
|
186
|
+
severity?: SecretSeverity;
|
|
187
|
+
confidence?: number;
|
|
188
|
+
handle: string;
|
|
189
|
+
token: string;
|
|
190
|
+
start: number;
|
|
191
|
+
end: number;
|
|
192
|
+
}
|
|
193
|
+
export interface ScanResult {
|
|
194
|
+
tokenizedText: string;
|
|
195
|
+
findings: ScanFinding[];
|
|
196
|
+
}
|
package/dist/types.js
ADDED
package/dist/unlock.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface KeychainInfo {
|
|
2
|
+
supported: boolean;
|
|
3
|
+
service: string;
|
|
4
|
+
account: string;
|
|
5
|
+
provider: "native-helper" | "security-cli" | "windows-helper" | "none";
|
|
6
|
+
helperPath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface UnlockStatus {
|
|
9
|
+
envConfigured: boolean;
|
|
10
|
+
activeSource: "env" | "macos-keychain" | "windows-credential-manager" | "none";
|
|
11
|
+
keychain: KeychainInfo & {
|
|
12
|
+
configured: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface MacKeychainItemRef {
|
|
16
|
+
service: string;
|
|
17
|
+
account: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function requireUnlockPassphrase(): string;
|
|
21
|
+
export declare function unlockStatus(): UnlockStatus;
|
|
22
|
+
export declare function setKeychainPassphrase(passphrase: string): void;
|
|
23
|
+
export declare function deleteKeychainPassphrase(): boolean;
|
|
24
|
+
export declare function hasKeychainPassphrase(): boolean;
|
|
25
|
+
export declare function keychainInfo(): KeychainInfo;
|
|
26
|
+
export declare function defaultSecretKeychainService(): string;
|
|
27
|
+
export declare function setMacKeychainItem(ref: MacKeychainItemRef, value: string): void;
|
|
28
|
+
export declare function getMacKeychainItem(ref: MacKeychainItemRef): string;
|
|
29
|
+
export declare function deleteMacKeychainItem(ref: MacKeychainItemRef): boolean;
|
package/dist/unlock.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const defaultService = "com.s-gw.sgw.master-passphrase";
|
|
7
|
+
const defaultSecretService = "com.s-gw.sgw.secret";
|
|
8
|
+
const nativeHelperName = "s-gw-keychain-helper";
|
|
9
|
+
const windowsCredentialHelperName = "s-gw-credential.ps1";
|
|
10
|
+
export function requireUnlockPassphrase() {
|
|
11
|
+
const fromEnv = process.env.SGW_MASTER_PASSPHRASE;
|
|
12
|
+
if (validPassphrase(fromEnv)) {
|
|
13
|
+
return fromEnv;
|
|
14
|
+
}
|
|
15
|
+
const fromKeychain = readKeychainPassphrase();
|
|
16
|
+
if (validPassphrase(fromKeychain)) {
|
|
17
|
+
return fromKeychain;
|
|
18
|
+
}
|
|
19
|
+
throw new Error("s-gw needs a local unlock passphrase. Run `s-gw unlock keychain set --value-stdin` or set SGW_MASTER_PASSPHRASE.");
|
|
20
|
+
}
|
|
21
|
+
export function unlockStatus() {
|
|
22
|
+
const envConfigured = validPassphrase(process.env.SGW_MASTER_PASSPHRASE);
|
|
23
|
+
const keychain = keychainInfo();
|
|
24
|
+
const configured = hasKeychainPassphrase();
|
|
25
|
+
let activeSource = "none";
|
|
26
|
+
if (envConfigured) {
|
|
27
|
+
activeSource = "env";
|
|
28
|
+
}
|
|
29
|
+
else if (configured) {
|
|
30
|
+
activeSource = process.platform === "win32" ? "windows-credential-manager" : "macos-keychain";
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
envConfigured,
|
|
34
|
+
activeSource,
|
|
35
|
+
keychain: {
|
|
36
|
+
...keychain,
|
|
37
|
+
configured
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function setKeychainPassphrase(passphrase) {
|
|
42
|
+
if (!validPassphrase(passphrase)) {
|
|
43
|
+
throw new Error("Unlock passphrase must be at least 8 characters.");
|
|
44
|
+
}
|
|
45
|
+
ensureLocalCredentialStore();
|
|
46
|
+
runKeychainSet(keychainInfo(), passphrase);
|
|
47
|
+
}
|
|
48
|
+
export function deleteKeychainPassphrase() {
|
|
49
|
+
ensureLocalCredentialStore();
|
|
50
|
+
try {
|
|
51
|
+
runKeychainDelete(keychainInfo());
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function hasKeychainPassphrase() {
|
|
59
|
+
if (!keychainInfo().supported || process.env.SGW_DISABLE_KEYCHAIN === "1") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const passphrase = readKeychainPassphrase();
|
|
63
|
+
return validPassphrase(passphrase);
|
|
64
|
+
}
|
|
65
|
+
export function keychainInfo() {
|
|
66
|
+
return {
|
|
67
|
+
supported: supportsLocalCredentialStore(),
|
|
68
|
+
service: process.env.SGW_KEYCHAIN_SERVICE || defaultService,
|
|
69
|
+
account: process.env.SGW_KEYCHAIN_ACCOUNT || os.userInfo().username || "local-user",
|
|
70
|
+
...keychainProvider()
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function defaultSecretKeychainService() {
|
|
74
|
+
return process.env.SGW_SECRET_KEYCHAIN_SERVICE || defaultSecretService;
|
|
75
|
+
}
|
|
76
|
+
export function setMacKeychainItem(ref, value) {
|
|
77
|
+
if (!value) {
|
|
78
|
+
throw new Error("Cannot store an empty Keychain item.");
|
|
79
|
+
}
|
|
80
|
+
const info = keychainInfoForItem(ref);
|
|
81
|
+
ensureNativeCredentialStore(info);
|
|
82
|
+
runKeychainSet(info, value, ref.label || "s-gw local secret");
|
|
83
|
+
}
|
|
84
|
+
export function getMacKeychainItem(ref) {
|
|
85
|
+
const info = keychainInfoForItem(ref);
|
|
86
|
+
ensureNativeCredentialStore(info);
|
|
87
|
+
return runKeychainGet(info).replace(/\r?\n$/, "");
|
|
88
|
+
}
|
|
89
|
+
export function deleteMacKeychainItem(ref) {
|
|
90
|
+
try {
|
|
91
|
+
const info = keychainInfoForItem(ref);
|
|
92
|
+
ensureNativeCredentialStore(info);
|
|
93
|
+
runKeychainDelete(info);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function readKeychainPassphrase() {
|
|
101
|
+
const info = keychainInfo();
|
|
102
|
+
if (!info.supported || process.env.SGW_DISABLE_KEYCHAIN === "1") {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const output = runKeychainGet(info);
|
|
107
|
+
return output.replace(/\r?\n$/, "");
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function ensureLocalCredentialStore() {
|
|
114
|
+
if (!keychainInfo().supported) {
|
|
115
|
+
throw new Error("OS credential-store unlock is only available on macOS or Windows.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function ensureNativeCredentialStore(info) {
|
|
119
|
+
if (!info.supported) {
|
|
120
|
+
throw new Error("OS credential-store secret storage is only available on macOS or Windows.");
|
|
121
|
+
}
|
|
122
|
+
if ((info.provider !== "native-helper" && info.provider !== "windows-helper") || !info.helperPath) {
|
|
123
|
+
throw new Error("OS credential-store secret storage requires the native s-gw helper. Run `npm run build:native`.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function keychainInfoForItem(ref) {
|
|
127
|
+
const service = ref.service.trim();
|
|
128
|
+
const account = ref.account.trim();
|
|
129
|
+
if (!service || !account) {
|
|
130
|
+
throw new Error("Keychain service and account are required.");
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
supported: supportsLocalCredentialStore(),
|
|
134
|
+
service,
|
|
135
|
+
account,
|
|
136
|
+
...keychainProvider()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function validPassphrase(value) {
|
|
140
|
+
return typeof value === "string" && value.trim().length >= 8;
|
|
141
|
+
}
|
|
142
|
+
function keychainProvider() {
|
|
143
|
+
if (process.platform === "win32") {
|
|
144
|
+
const helperPath = findWindowsCredentialHelper();
|
|
145
|
+
return helperPath ? { provider: "windows-helper", helperPath } : { provider: "none" };
|
|
146
|
+
}
|
|
147
|
+
if (process.platform !== "darwin") {
|
|
148
|
+
return { provider: "none" };
|
|
149
|
+
}
|
|
150
|
+
const helperPath = findNativeHelper();
|
|
151
|
+
if (helperPath) {
|
|
152
|
+
return { provider: "native-helper", helperPath };
|
|
153
|
+
}
|
|
154
|
+
if (process.env.SGW_ALLOW_SECURITY_CLI === "1") {
|
|
155
|
+
return { provider: "security-cli" };
|
|
156
|
+
}
|
|
157
|
+
return { provider: "none" };
|
|
158
|
+
}
|
|
159
|
+
function supportsLocalCredentialStore() {
|
|
160
|
+
return process.platform === "darwin" || process.platform === "win32";
|
|
161
|
+
}
|
|
162
|
+
function findNativeHelper() {
|
|
163
|
+
const fromEnv = process.env.SGW_KEYCHAIN_HELPER;
|
|
164
|
+
if (fromEnv && existsSync(fromEnv)) {
|
|
165
|
+
return fromEnv;
|
|
166
|
+
}
|
|
167
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
168
|
+
const candidates = [
|
|
169
|
+
path.resolve(here, "native", nativeHelperName),
|
|
170
|
+
path.resolve(process.cwd(), "dist", "native", nativeHelperName)
|
|
171
|
+
];
|
|
172
|
+
return candidates.find((candidate) => existsSync(candidate));
|
|
173
|
+
}
|
|
174
|
+
function findWindowsCredentialHelper() {
|
|
175
|
+
const fromEnv = process.env.SGW_WINDOWS_CREDENTIAL_HELPER || process.env.SGW_KEYCHAIN_HELPER;
|
|
176
|
+
if (fromEnv && existsSync(fromEnv)) {
|
|
177
|
+
return fromEnv;
|
|
178
|
+
}
|
|
179
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
180
|
+
const candidates = [
|
|
181
|
+
path.resolve(here, "windows", windowsCredentialHelperName),
|
|
182
|
+
path.resolve(process.cwd(), "dist", "windows", windowsCredentialHelperName)
|
|
183
|
+
];
|
|
184
|
+
return candidates.find((candidate) => existsSync(candidate));
|
|
185
|
+
}
|
|
186
|
+
function runKeychainGet(info) {
|
|
187
|
+
if (info.provider === "native-helper" && info.helperPath) {
|
|
188
|
+
return runNativeHelper(info.helperPath, ["get", "--service", info.service, "--account", info.account]);
|
|
189
|
+
}
|
|
190
|
+
if (info.provider === "security-cli") {
|
|
191
|
+
return runSecurity([
|
|
192
|
+
"find-generic-password",
|
|
193
|
+
"-a",
|
|
194
|
+
info.account,
|
|
195
|
+
"-s",
|
|
196
|
+
info.service,
|
|
197
|
+
"-w"
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
200
|
+
if (info.provider === "windows-helper" && info.helperPath) {
|
|
201
|
+
return runWindowsCredentialHelper(info.helperPath, ["get", "-Service", info.service, "-Account", info.account]);
|
|
202
|
+
}
|
|
203
|
+
throw new Error("No local OS credential-store provider is available. Run `npm run build:native`.");
|
|
204
|
+
}
|
|
205
|
+
function runKeychainSet(info, passphrase, label = "s-gw local unlock passphrase") {
|
|
206
|
+
if (info.provider === "native-helper" && info.helperPath) {
|
|
207
|
+
runNativeHelper(info.helperPath, ["set", "--service", info.service, "--account", info.account, "--label", label], passphrase);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (info.provider === "security-cli") {
|
|
211
|
+
runSecurity([
|
|
212
|
+
"add-generic-password",
|
|
213
|
+
"-U",
|
|
214
|
+
"-a",
|
|
215
|
+
info.account,
|
|
216
|
+
"-s",
|
|
217
|
+
info.service,
|
|
218
|
+
"-l",
|
|
219
|
+
label,
|
|
220
|
+
"-w",
|
|
221
|
+
passphrase
|
|
222
|
+
]);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (info.provider === "windows-helper" && info.helperPath) {
|
|
226
|
+
runWindowsCredentialHelper(info.helperPath, ["set", "-Service", info.service, "-Account", info.account, "-Label", label], passphrase);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
throw new Error("No local OS credential-store provider is available. Run `npm run build:native`.");
|
|
230
|
+
}
|
|
231
|
+
function runKeychainDelete(info) {
|
|
232
|
+
if (info.provider === "native-helper" && info.helperPath) {
|
|
233
|
+
runNativeHelper(info.helperPath, ["delete", "--service", info.service, "--account", info.account]);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (info.provider === "security-cli") {
|
|
237
|
+
runSecurity(["delete-generic-password", "-a", info.account, "-s", info.service]);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (info.provider === "windows-helper" && info.helperPath) {
|
|
241
|
+
runWindowsCredentialHelper(info.helperPath, ["delete", "-Service", info.service, "-Account", info.account]);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
throw new Error("No local OS credential-store provider is available. Run `npm run build:native`.");
|
|
245
|
+
}
|
|
246
|
+
function runNativeHelper(helperPath, args, input) {
|
|
247
|
+
const result = spawnSync(helperPath, args, {
|
|
248
|
+
input,
|
|
249
|
+
encoding: "utf8",
|
|
250
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
251
|
+
});
|
|
252
|
+
if (result.status !== 0) {
|
|
253
|
+
throw new Error(result.stderr.trim() || `Native Keychain helper failed with status ${result.status}`);
|
|
254
|
+
}
|
|
255
|
+
return result.stdout;
|
|
256
|
+
}
|
|
257
|
+
function runSecurity(args) {
|
|
258
|
+
return execFileSync("security", args, {
|
|
259
|
+
encoding: "utf8",
|
|
260
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function runWindowsCredentialHelper(helperPath, args, input) {
|
|
264
|
+
const result = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", helperPath, ...args], {
|
|
265
|
+
input,
|
|
266
|
+
encoding: "utf8",
|
|
267
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
268
|
+
});
|
|
269
|
+
if (result.status !== 0) {
|
|
270
|
+
throw new Error(result.stderr.trim() || `Windows Credential Manager helper failed with status ${result.status}`);
|
|
271
|
+
}
|
|
272
|
+
return result.stdout;
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=unlock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
s-gw 0.1.0
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
[CmdletBinding()]
|
|
2
|
+
param(
|
|
3
|
+
[int]$Port = 8718,
|
|
4
|
+
[string]$ConsoleUrl = "",
|
|
5
|
+
[switch]$NoStart
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
$ErrorActionPreference = "Stop"
|
|
9
|
+
|
|
10
|
+
function Resolve-CliPath {
|
|
11
|
+
if ($env:SGW_CLI_PATH -and (Test-Path -LiteralPath $env:SGW_CLI_PATH)) {
|
|
12
|
+
return (Resolve-Path -LiteralPath $env:SGW_CLI_PATH).Path
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
$distDir = Split-Path -Parent $PSScriptRoot
|
|
16
|
+
$candidate = Join-Path $distDir "cli.js"
|
|
17
|
+
if (Test-Path -LiteralPath $candidate) {
|
|
18
|
+
return (Resolve-Path -LiteralPath $candidate).Path
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw "Unable to find s-gw CLI. Set SGW_CLI_PATH to dist\cli.js."
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Resolve-NodePath {
|
|
25
|
+
if ($env:SGW_NODE_PATH) {
|
|
26
|
+
return $env:SGW_NODE_PATH
|
|
27
|
+
}
|
|
28
|
+
return "node"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function New-ConsoleUrl {
|
|
32
|
+
if ($ConsoleUrl.Trim()) {
|
|
33
|
+
return $ConsoleUrl.Trim()
|
|
34
|
+
}
|
|
35
|
+
return "http://127.0.0.1:$Port/"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Get-OriginUrl([string]$Url) {
|
|
39
|
+
$uri = [Uri]$Url
|
|
40
|
+
return $uri.GetLeftPart([UriPartial]::Authority) + "/"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function Test-ConsoleReady([string]$Url) {
|
|
44
|
+
$origin = Get-OriginUrl $Url
|
|
45
|
+
try {
|
|
46
|
+
$health = Invoke-RestMethod -Method Get -Uri ($origin + "api/health") -TimeoutSec 1
|
|
47
|
+
return ($health.ok -eq $true)
|
|
48
|
+
} catch {
|
|
49
|
+
return $false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Start-ConsoleDaemon([string]$CliPath, [string]$NodePath) {
|
|
54
|
+
$logs = Join-Path $env:LOCALAPPDATA "s-gw\logs"
|
|
55
|
+
New-Item -ItemType Directory -Force -Path $logs | Out-Null
|
|
56
|
+
|
|
57
|
+
$args = @($CliPath, "console", "--host", "127.0.0.1", "--port", [string]$Port, "--no-open")
|
|
58
|
+
$root = Split-Path -Parent (Split-Path -Parent $CliPath)
|
|
59
|
+
|
|
60
|
+
Start-Process `
|
|
61
|
+
-FilePath $NodePath `
|
|
62
|
+
-ArgumentList $args `
|
|
63
|
+
-WorkingDirectory $root `
|
|
64
|
+
-WindowStyle Hidden `
|
|
65
|
+
-RedirectStandardOutput (Join-Path $logs "console.log") `
|
|
66
|
+
-RedirectStandardError (Join-Path $logs "console.err.log") | Out-Null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Wait-Console([string]$Url) {
|
|
70
|
+
for ($i = 0; $i -lt 30; $i += 1) {
|
|
71
|
+
if (Test-ConsoleReady $Url) {
|
|
72
|
+
return $true
|
|
73
|
+
}
|
|
74
|
+
Start-Sleep -Milliseconds 250
|
|
75
|
+
}
|
|
76
|
+
return $false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function Open-ConsoleWindow([string]$Url) {
|
|
80
|
+
$edge = Get-Command "msedge.exe" -ErrorAction SilentlyContinue
|
|
81
|
+
if ($edge) {
|
|
82
|
+
Start-Process -FilePath $edge.Source -ArgumentList @("--app=$Url") | Out-Null
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
$chrome = Get-Command "chrome.exe" -ErrorAction SilentlyContinue
|
|
87
|
+
if ($chrome) {
|
|
88
|
+
Start-Process -FilePath $chrome.Source -ArgumentList @("--app=$Url") | Out-Null
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Start-Process $Url | Out-Null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$url = New-ConsoleUrl
|
|
96
|
+
$cliPath = Resolve-CliPath
|
|
97
|
+
$nodePath = Resolve-NodePath
|
|
98
|
+
|
|
99
|
+
if (-not $NoStart -and -not (Test-ConsoleReady $url)) {
|
|
100
|
+
Start-ConsoleDaemon -CliPath $cliPath -NodePath $nodePath
|
|
101
|
+
if (-not (Wait-Console $url)) {
|
|
102
|
+
throw "s-gw console did not become ready at $url. Check $env:LOCALAPPDATA\s-gw\logs."
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Open-ConsoleWindow $url
|