sigrank 1.0.45 → 1.0.47
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/CHANGELOG.md +28 -0
- package/README.md +6 -3
- package/adapters/index.mjs +2 -0
- package/adapters/tokenpull.mjs +1 -1
- package/identity/keystore.mjs +3 -1
- package/manifest.json +1 -1
- package/package.json +3 -3
- package/presentation/cli.mjs +9 -4
- package/presentation/tui-themes.mjs +43 -28
- package/presentation/tui.mjs +960 -269
- package/proxy.mjs +16 -0
- package/resources/privacy-model.md +7 -4
- package/tokscale_analytics.mjs +0 -1
- package/tools/_helpers.mjs +6 -0
- package/tools/enroll.mjs +17 -3
- package/tools/submit-verified.mjs +13 -1
- package/tools/tokscale-developer-profile.mjs +1 -1
package/proxy.mjs
CHANGED
|
@@ -311,6 +311,22 @@ export async function startProxy({
|
|
|
311
311
|
};
|
|
312
312
|
|
|
313
313
|
const server = http.createServer(async (req, res) => {
|
|
314
|
+
// DNS-rebinding / CSRF guard: only accept loopback Host headers on our own port, and
|
|
315
|
+
// refuse browser cross-site requests (Sec-Fetch-Site other than same-origin/none).
|
|
316
|
+
const hostHeader = String(req.headers.host || "");
|
|
317
|
+
const hm = /^(127\.0\.0\.1|localhost|\[::1\])(?::(\d+))?$/i.exec(hostHeader);
|
|
318
|
+
const boundPort = server.address()?.port;
|
|
319
|
+
const sfs = req.headers["sec-fetch-site"];
|
|
320
|
+
if (
|
|
321
|
+
!hm ||
|
|
322
|
+
(boundPort && Number(hm[2] || 80) !== boundPort) ||
|
|
323
|
+
(sfs !== undefined && sfs !== "same-origin" && sfs !== "none")
|
|
324
|
+
) {
|
|
325
|
+
res.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
|
|
326
|
+
res.end("Forbidden\n");
|
|
327
|
+
req.resume();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
314
330
|
let parsedUrl;
|
|
315
331
|
try {
|
|
316
332
|
parsedUrl = new URL(req.url || "/", `http://${req.headers.host || host}`);
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## What leaves your machine
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**The usage content of every submission is four numbers:**
|
|
6
6
|
- Input tokens
|
|
7
7
|
- Output tokens
|
|
8
8
|
- Cache create tokens
|
|
9
9
|
- Cache read tokens
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The signed path additionally carries: codename, device id, device public key, ed25519 signature, agent version, platform, message/session counts, and the weekly report block. `enroll` sends a device label (default `sigrank <version> · <platform>`; your hostname is not sent unless you set `SIGRANK_DEVICE_LABEL` or pass `device_label` yourself).
|
|
12
|
+
|
|
13
|
+
Never sent: prompts, responses, code, file contents, file paths, repo or project names, or environment variables. The server re-parses and re-scores the pillars authoritatively.
|
|
12
14
|
|
|
13
15
|
The optional `sigrank proxy` is a local Upsilon transport path to Anthropic/OpenAI, not a
|
|
14
16
|
submission path to the SigRank leaderboard. When explicitly enabled, it necessarily receives and
|
|
@@ -32,8 +34,9 @@ only provider-reported token counts, model/backend metadata, and timestamps to
|
|
|
32
34
|
- Your prompts or messages
|
|
33
35
|
- Your code or file contents
|
|
34
36
|
- Your tool calls or their results
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
+
- Your hostname (unless you opt in via a device label)
|
|
38
|
+
- File paths, repo/project names, or environment variables
|
|
39
|
+
- Your identity (only your chosen codename; the platform name, agent version, and message/session counts are sent)
|
|
37
40
|
|
|
38
41
|
The local proxy process is different from the Upsilon service: if you opt in, it
|
|
39
42
|
handles your provider traffic in memory solely to forward it to Anthropic or
|
package/tokscale_analytics.mjs
CHANGED
|
@@ -356,7 +356,6 @@ export async function tokscaleDeveloperProfile() {
|
|
|
356
356
|
sessions_path_exists: c.sessions_path_exists ?? null,
|
|
357
357
|
headless_supported: c.headless_supported ?? null,
|
|
358
358
|
workspace_count: c.workspaces.size,
|
|
359
|
-
workspaces: [...c.workspaces].sort(),
|
|
360
359
|
models: c.models,
|
|
361
360
|
};
|
|
362
361
|
});
|
package/tools/_helpers.mjs
CHANGED
|
@@ -32,6 +32,8 @@ const ALLOWED_API_HOSTS = new Set([
|
|
|
32
32
|
"sigeconomy.com",
|
|
33
33
|
"localhost",
|
|
34
34
|
"127.0.0.1",
|
|
35
|
+
"::1",
|
|
36
|
+
"[::1]",
|
|
35
37
|
"test.local", // test fixtures only
|
|
36
38
|
]);
|
|
37
39
|
|
|
@@ -42,6 +44,10 @@ export function validateApiBase(urlStr) {
|
|
|
42
44
|
if (!ALLOWED_API_HOSTS.has(u.hostname)) {
|
|
43
45
|
throw new Error(`Blocked: API host '${u.hostname}' is not in the allowlist`);
|
|
44
46
|
}
|
|
47
|
+
const loopback = ["localhost", "127.0.0.1", "::1", "[::1]", "test.local"].includes(u.hostname);
|
|
48
|
+
if (u.protocol !== "https:" && !(loopback && u.protocol === "http:")) {
|
|
49
|
+
throw new Error(`Blocked: API base must use https (http only for loopback): ${u.protocol}//${u.hostname}`);
|
|
50
|
+
}
|
|
45
51
|
return urlStr;
|
|
46
52
|
} catch (e) {
|
|
47
53
|
if (e.message.startsWith("Blocked:")) throw e;
|
package/tools/enroll.mjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* tools/enroll.mjs — enroll tool.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import os from "node:os";
|
|
6
5
|
import { ensureIdentity, recordEnrollment } from "../identity/keystore.mjs";
|
|
7
6
|
import { ENROLL_OUTPUT, ANNOTATIONS } from "./_schemas.mjs";
|
|
8
7
|
import { TERMS_VERSION, PRIVACY_VERSION } from "../lib/constants.mjs";
|
|
@@ -28,7 +27,7 @@ export const TOOL_DEF = {
|
|
|
28
27
|
device_label: {
|
|
29
28
|
type: "string",
|
|
30
29
|
description:
|
|
31
|
-
"optional label for this device (default:
|
|
30
|
+
"optional label for this device (default: sigrank <version> · <platform>; hostnames are never sent unless you pass a label yourself)",
|
|
32
31
|
},
|
|
33
32
|
},
|
|
34
33
|
required: ["code"],
|
|
@@ -45,8 +44,23 @@ export async function handleEnroll(args, ctx) {
|
|
|
45
44
|
"enroll requires a `code` — paste your connect code from signalaf.com → Settings → Connect a device.",
|
|
46
45
|
);
|
|
47
46
|
const id = ctx.opts.identity || ensureIdentity();
|
|
47
|
+
// Re-enroll guard: a model-invoked MCP call must never silently re-bind a device that
|
|
48
|
+
// already has an operator. The human CLI/TUI path sets opts.allowReenroll after its own
|
|
49
|
+
// explicit consent prompt.
|
|
50
|
+
if (id.codename && !ctx.opts.allowReenroll) {
|
|
51
|
+
return {
|
|
52
|
+
status: "already_enrolled",
|
|
53
|
+
codename: id.codename,
|
|
54
|
+
operator_id: id.operator_id ?? null,
|
|
55
|
+
device_id: id.device_id,
|
|
56
|
+
detail:
|
|
57
|
+
"This device is already enrolled. To re-enroll, run `npx sigrank enroll` in a terminal yourself; the MCP tool will not re-bind an enrolled device.",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
48
60
|
const deviceLabel = String(
|
|
49
|
-
args?.device_label ||
|
|
61
|
+
args?.device_label ||
|
|
62
|
+
process.env.SIGRANK_DEVICE_LABEL ||
|
|
63
|
+
`sigrank ${id.agent_version} · ${process.platform}`,
|
|
50
64
|
).slice(0, 120);
|
|
51
65
|
const res = await ctx.doFetch(`${ctx.apiBase}/api/v1/devices/enroll`, {
|
|
52
66
|
method: "POST",
|
|
@@ -12,7 +12,7 @@ import { _computeReportBlock } from "./_report.mjs";
|
|
|
12
12
|
export const TOOL_DEF = {
|
|
13
13
|
name: "submit_verified",
|
|
14
14
|
description:
|
|
15
|
-
"Publish your LOCAL token runs to the SigRank board as a VERIFIED operator — the enrolled, signed path. Reads your pillars (tokenpull), builds the canonical Schema 1.0 snapshot per window, ed25519-signs it with your device key, and POSTs to /api/v1/snapshots. Requires `npx sigrank-mcp enroll` first (a bound device). Only signed submissions from a trusted device rank on the board. Token-only; the private key never leaves your machine.
|
|
15
|
+
"Publish your LOCAL token runs to the SigRank board as a VERIFIED operator — the enrolled, signed path. Reads your pillars (tokenpull), builds the canonical Schema 1.0 snapshot per window, ed25519-signs it with your device key, and POSTs to /api/v1/snapshots. Requires `npx sigrank-mcp enroll` first (a bound device). Only signed submissions from a trusted device rank on the board. Token-only; the private key never leaves your machine. Publishing requires `confirm: true` (ask the user first); pass dry_run:true to inspect the exact signed payload without publishing and without confirm.",
|
|
16
16
|
annotations: {
|
|
17
17
|
title: "Submit verified score",
|
|
18
18
|
...ANNOTATIONS.destructiveHint,
|
|
@@ -33,6 +33,11 @@ export const TOOL_DEF = {
|
|
|
33
33
|
description:
|
|
34
34
|
"source platform (default: claude). 'multi' = combined cascade summed across all locally-detected platforms (needs 2+ active); empty windows are skipped.",
|
|
35
35
|
},
|
|
36
|
+
confirm: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description:
|
|
39
|
+
"REQUIRED to publish: must be true, set only after the user has explicitly approved submitting. Not needed with dry_run:true.",
|
|
40
|
+
},
|
|
36
41
|
dry_run: {
|
|
37
42
|
type: "boolean",
|
|
38
43
|
description:
|
|
@@ -52,6 +57,13 @@ export async function handleSubmitVerified(args, ctx) {
|
|
|
52
57
|
detail: "Run `npx sigrank-mcp enroll` to bind this device first.",
|
|
53
58
|
};
|
|
54
59
|
}
|
|
60
|
+
if (!args?.dry_run && args?.confirm !== true) {
|
|
61
|
+
return {
|
|
62
|
+
status: "confirmation_required",
|
|
63
|
+
detail:
|
|
64
|
+
"submit_verified publishes to the public board. Ask the user for approval, then call again with confirm: true (or use dry_run: true to preview).",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
55
67
|
const platform = args?.platform || "claude";
|
|
56
68
|
|
|
57
69
|
// MULTI: the combined cross-platform cascade. The dashboard already SUMS every
|
|
@@ -20,7 +20,7 @@ export const TOOL_DEF = {
|
|
|
20
20
|
properties: {
|
|
21
21
|
tools: {
|
|
22
22
|
type: "array",
|
|
23
|
-
description: "Per-tool profile with model mix, token pillars, session counts,
|
|
23
|
+
description: "Per-tool profile with model mix, token pillars, session counts, workspace count",
|
|
24
24
|
},
|
|
25
25
|
summary: {
|
|
26
26
|
type: "object",
|