token-goat 2.9.4 → 2.9.6
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/README.md +7 -4
- package/dist/{token-goat-chunk-CBO45DYZ.mjs → token-goat-chunk-3HJQR4OO.mjs} +9778 -9438
- package/dist/{token-goat-chunk-GEXHIIKM.mjs → token-goat-chunk-3ZAMYHQG.mjs} +5 -5
- package/dist/{token-goat-chunk-US57I7GN.mjs → token-goat-chunk-46VKOCUH.mjs} +241 -33
- package/dist/{token-goat-chunk-FSC44QOH.mjs → token-goat-chunk-7GJBID7S.mjs} +2 -2
- package/dist/{token-goat-chunk-OZAEJQ7L.mjs → token-goat-chunk-C7LLUVUT.mjs} +3 -3
- package/dist/{token-goat-chunk-63XEQMNU.mjs → token-goat-chunk-EIZYCVBM.mjs} +220 -31
- package/dist/{token-goat-chunk-6MU6FHE3.mjs → token-goat-chunk-J7LGMKF3.mjs} +82 -64
- package/dist/{token-goat-chunk-EPX5DWGM.mjs → token-goat-chunk-LXIC7MTW.mjs} +2 -1
- package/dist/{token-goat-chunk-3ZR4NLB3.mjs → token-goat-chunk-T2OE7MYM.mjs} +370 -105
- package/dist/{token-goat-chunk-EQKNR7CN.mjs → token-goat-chunk-TX64YIIV.mjs} +5 -5
- package/dist/{token-goat-chunk-5BHXZEOI.mjs → token-goat-chunk-UJORWTI4.mjs} +2 -2
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/docs/security.md +8 -4
- package/package.json +1 -1
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
buildEvent,
|
|
5
5
|
relay,
|
|
6
6
|
relayInProcess
|
|
7
|
-
} from "./token-goat-chunk-
|
|
7
|
+
} from "./token-goat-chunk-EIZYCVBM.mjs";
|
|
8
8
|
import {
|
|
9
9
|
MAX_STDIN_BYTES,
|
|
10
10
|
readStdinJson
|
|
11
|
-
} from "./token-goat-chunk-
|
|
12
|
-
import "./token-goat-chunk-
|
|
13
|
-
import "./token-goat-chunk-
|
|
14
|
-
import "./token-goat-chunk-
|
|
11
|
+
} from "./token-goat-chunk-7GJBID7S.mjs";
|
|
12
|
+
import "./token-goat-chunk-3HJQR4OO.mjs";
|
|
13
|
+
import "./token-goat-chunk-LXIC7MTW.mjs";
|
|
14
|
+
import "./token-goat-chunk-T2OE7MYM.mjs";
|
|
15
15
|
import "./token-goat-chunk-EEIDFMEM.mjs";
|
|
16
16
|
import "./token-goat-chunk-A37V4PBF.mjs";
|
|
17
17
|
export {
|
|
@@ -3,7 +3,7 @@ const require = __cjsRequire(import.meta.url);
|
|
|
3
3
|
import {
|
|
4
4
|
deliveredOutputBytes,
|
|
5
5
|
wrappedShell
|
|
6
|
-
} from "./token-goat-chunk-
|
|
6
|
+
} from "./token-goat-chunk-LXIC7MTW.mjs";
|
|
7
7
|
import {
|
|
8
8
|
ToolFilter,
|
|
9
9
|
capTokens,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
recordStat,
|
|
16
16
|
selectFilter,
|
|
17
17
|
shlexSplit
|
|
18
|
-
} from "./token-goat-chunk-
|
|
18
|
+
} from "./token-goat-chunk-T2OE7MYM.mjs";
|
|
19
19
|
import "./token-goat-chunk-EEIDFMEM.mjs";
|
|
20
20
|
import {
|
|
21
21
|
init_define_import_meta_env
|
package/dist/token-goat-hook.mjs
CHANGED
|
@@ -2,11 +2,11 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
relayInProcess
|
|
5
|
-
} from "./token-goat-chunk-
|
|
6
|
-
import "./token-goat-chunk-
|
|
7
|
-
import "./token-goat-chunk-
|
|
8
|
-
import "./token-goat-chunk-
|
|
9
|
-
import "./token-goat-chunk-
|
|
5
|
+
} from "./token-goat-chunk-EIZYCVBM.mjs";
|
|
6
|
+
import "./token-goat-chunk-7GJBID7S.mjs";
|
|
7
|
+
import "./token-goat-chunk-3HJQR4OO.mjs";
|
|
8
|
+
import "./token-goat-chunk-LXIC7MTW.mjs";
|
|
9
|
+
import "./token-goat-chunk-T2OE7MYM.mjs";
|
|
10
10
|
import "./token-goat-chunk-EEIDFMEM.mjs";
|
|
11
11
|
import {
|
|
12
12
|
init_define_import_meta_env
|
package/dist/token-goat.core.mjs
CHANGED
|
@@ -2,13 +2,13 @@ import { createRequire as __cjsRequire } from 'node:module';
|
|
|
2
2
|
const require = __cjsRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
run
|
|
5
|
-
} from "./token-goat-chunk-
|
|
6
|
-
import "./token-goat-chunk-
|
|
7
|
-
import "./token-goat-chunk-
|
|
8
|
-
import "./token-goat-chunk-
|
|
5
|
+
} from "./token-goat-chunk-J7LGMKF3.mjs";
|
|
6
|
+
import "./token-goat-chunk-46VKOCUH.mjs";
|
|
7
|
+
import "./token-goat-chunk-7GJBID7S.mjs";
|
|
8
|
+
import "./token-goat-chunk-3HJQR4OO.mjs";
|
|
9
9
|
import {
|
|
10
10
|
installEpipeGuard
|
|
11
|
-
} from "./token-goat-chunk-
|
|
11
|
+
} from "./token-goat-chunk-T2OE7MYM.mjs";
|
|
12
12
|
import "./token-goat-chunk-EEIDFMEM.mjs";
|
|
13
13
|
import {
|
|
14
14
|
init_define_import_meta_env
|
package/docs/security.md
CHANGED
|
@@ -20,9 +20,9 @@ Outbound network is reserved to these explicit cases:
|
|
|
20
20
|
|
|
21
21
|
**One switch for all of it.** Set `network.offline = true` (env `TOKEN_GOAT_OFFLINE`) and every one of the paths above refuses instead of connecting, saying so rather than failing quietly. Anything already cached keeps working: a machine that has the embedding model still runs `semantic`, and one that has the language data still reads text out of images. This is one of the settings a per-project config file may not touch, so cloning a repository cannot switch it back off.
|
|
22
22
|
|
|
23
|
-
**A repository cannot reconfigure the security controls.** A project-root `.token-goat.toml` layers on top of your global config, which is what it is for: hint thresholds, indexing settings, compression tuning. But that file arrives with the repository, so whoever wrote the repository wrote it. Seven whole sections are therefore off limits to it,
|
|
23
|
+
**A repository cannot reconfigure the security controls.** A project-root `.token-goat.toml` layers on top of your global config, which is what it is for: hint thresholds, indexing settings, compression tuning. But that file arrives with the repository, so whoever wrote the repository wrote it. Seven whole sections are therefore off limits to it, and come from your global config or the environment only: `injection` (prompt-injection fencing), `webfetch` (the fetch allow and deny lists), `gdrive` (the Google Drive integration), `mcp` (root confinement and the allowed-roots list), `network` (offline mode), `redaction` (the secret-redaction rules), and `screenshot` (the headless browser). Twelve individual keys inside otherwise-overridable sections are locked the same way. Five decide what gets indexed at all: `indexing.cross_project_symbols`, `indexing.skip_dirs`, `indexing.skip_files`, `indexing.large_file_skip_kb` and `indexing.large_file_symbol_only_kb`, which matter because an unindexed file answers `symbol`, `read` and `semantic` in the same words a name that never existed does. Five more are the switches that decide whether a large file arrives folded rather than whole: `hints.fold_code_bodies`, `hints.fold_comment_blocks`, `hints.fold_prose_paragraphs`, `hints.outline_large_documents` and `hints.skeleton_large_sources`. The last two are `image_shrink.max_image_pixels`, the decompression-bomb cap, and `worker.blocked_roots`, the folders you have kept out of the index. A project file that sets one of them is ignored, and token-goat prints a line naming what it dropped. Everything else stays project-overridable.
|
|
24
24
|
|
|
25
|
-
**The lock covers the config file, not the environment.** These settings still read a `TOKEN_GOAT_*` environment variable, and a repository has ways to set one: a `.envrc` for direnv, a `terminal.integrated.env.*` block in a committed `.vscode/settings.json`, a `containerEnv` entry in a devcontainer. Refusing environment overrides would break the operator who exports a variable in their own shell, which is the legitimate case and the common one, so token-goat reports instead of refusing: `token-goat doctor` prints a `Security config overrides` line naming every locked security setting the environment is currently deciding, and the variable to unset. It covers all
|
|
25
|
+
**The lock covers the config file, not the environment.** These settings still read a `TOKEN_GOAT_*` environment variable, and a repository has ways to set one: a `.envrc` for direnv, a `terminal.integrated.env.*` block in a committed `.vscode/settings.json`, a `containerEnv` entry in a devcontainer. Refusing environment overrides would break the operator who exports a variable in their own shell, which is the legitimate case and the common one, so token-goat reports instead of refusing: `token-goat doctor` prints a `Security config overrides` line naming every locked security setting the environment is currently deciding, and the variable to unset. It covers all twenty locked keys that read an environment variable, and it derives that set from the same two tables that define what a project config may not write, rather than from a list kept alongside them. Settings with a safe side (booleans) are reported when the environment holds them open; settings without one (lists, sizes) are reported whenever the environment supplies a value at all, since there is nothing to compare against. A default install, where nothing is set, prints a single ok line.
|
|
26
26
|
|
|
27
27
|
**Security reports.** See [SECURITY.md](../SECURITY.md). Email `token-goat@dfkhelper.com`; do not file as a GitHub issue. Reports are acknowledged within 7 days; coordinated disclosure with a 90-day default window.
|
|
28
28
|
|
|
@@ -44,6 +44,10 @@ The line that decides is substitution: wherever token-goat replaces a tool resul
|
|
|
44
44
|
|
|
45
45
|
In every one of those cases the fence wraps the third-party bytes and stops there. Token-goat's own notice, the filter's marker, and the pointer telling the model how to recall the full output all sit outside the closing tag, because the fence is the model's one signal for where token-goat stops speaking. Fold them inside and that signal is gone, and anyone who guesses the marker's wording can write a line the model reads as token-goat's own.
|
|
46
46
|
|
|
47
|
+
Guessing the wording is not hard, so the fence does not rely on it staying secret: inside the fence, a line shaped like either voice token-goat speaks in has its opening bracket escaped. That covers `[token-goat: ...]`, the marker hooks sign a rewrite with, and `[tg]`, the prefix on every denial, which is the one that matters more because a denial is the only message token-goat sends that is shaped as an instruction to obey. The escape is narrow enough to leave ordinary bracketed words alone. Fenced file content is also placed below token-goat's own notice rather than above it, so no byte of a file can arrive ahead of token-goat speaking and be read as its preamble.
|
|
48
|
+
|
|
49
|
+
`token-goat ask` is fenced for a sharper reason than the rest. It retrieves indexed symbol bodies and pipes them to whatever `TOKEN_GOAT_ASK_BACKEND` names, normally `claude` or `codex`, so it is the one place where third-party text reaches a model that holds tools instead of a model reading a tool result. The snippets are redacted and then fenced before they enter the prompt; the question, which is yours, stays outside the fence.
|
|
50
|
+
|
|
47
51
|
Read is the exception: file content passes through to the model unfiltered, because filtering it would silently break legitimate use cases. Where token-goat splices a piece of a file into its own hint or denial message, that excerpt is fenced. Outside of the fence, the primary defense is the model's own training to treat tool output as data, not as commands from a trusted party.
|
|
48
52
|
|
|
49
53
|
Bash output that token-goat does not rewrite is the same kind of exception, and worth stating plainly rather than leaving to be inferred from the paragraph above. A command whose output is short, or too incompressible to be worth touching, reaches the model exactly as the harness delivered it, with no fence. So does output whose only change was stripping the colour codes a terminal would have rendered, since that path emits the command's own bytes and adds nothing of token-goat's to delimit. Fencing those cases would mean rewriting the result of every shell command an agent runs, a permanent cost on the most-used path in the tool, to re-label bytes the model was going to receive in that form anyway. The fence is worth its bytes where token-goat has substituted its own account of the output and the model can no longer tell whose words are whose. Where token-goat has stayed out of the way, the defense is the same as for Read: the model's training to treat tool output as data.
|
|
@@ -56,13 +60,13 @@ A third case needs no fence, because the danger is the line break rather than th
|
|
|
56
60
|
|
|
57
61
|
The MCP tools (`symbol` when given a `file` filter, `read`, `section`, `skeleton`, `outline`, `refs`, `brief`, `grep`, `imports`, `exports`) are confined to the project root, resolving symlinks before the check. Set `mcp.confine_reads_to_project_root = false` (env `TOKEN_GOAT_MCP_CONFINE_READS`) in your global config if you genuinely need cross-root reads from an MCP client; a per-project file cannot set it. The CLI is deliberately unconfined and unchanged. This is defense in depth for one sink, not a sandbox: an agent that can call these tools can usually call its own read tool too.
|
|
58
62
|
|
|
59
|
-
Note what that flag does and does not cover. It stops a caller traversing *out of* the root it is given; it does not constrain *which* root the caller supplies. Every MCP tool takes an optional `projectRoot`, and it exists for a reason — the server's cwd is often not the workspace root for MCP clients — but tool arguments are model-generated, so that choice is untrusted input like any other. If your deployment treats MCP as the only path to the filesystem, set `mcp.allowed_roots` (env `TOKEN_GOAT_MCP_ALLOWED_ROOTS`, delimiter-separated like `PATH`) to the roots that may legitimately be named; a resolved root outside every entry is then refused. It is empty by default, which keeps the multi-root behavior above unchanged.
|
|
63
|
+
Note what that flag does and does not cover. It stops a caller traversing *out of* the root it is given; it does not constrain *which* root the caller supplies. Every MCP tool takes an optional `projectRoot`, and it exists for a reason — the server's cwd is often not the workspace root for MCP clients — but tool arguments are model-generated, so that choice is untrusted input like any other. If your deployment treats MCP as the only path to the filesystem, set `mcp.allowed_roots` (env `TOKEN_GOAT_MCP_ALLOWED_ROOTS`, delimiter-separated like `PATH`) to the roots that may legitimately be named; a resolved root outside every entry is then refused. It is empty by default, which keeps the multi-root behavior above unchanged. Because empty means "any root may be named", `token-goat doctor` says that outright on its `Security mcp roots` line rather than reporting the confinement flag alone, which on its own reads as a stronger guarantee than it is.
|
|
60
64
|
|
|
61
65
|
**Restricting what token-goat may fetch.** `webfetch.allow` and `webfetch.deny` (env `TOKEN_GOAT_WEBFETCH_ALLOW` / `TOKEN_GOAT_WEBFETCH_DENY`, comma-separated) are wildcard URL patterns that decide which addresses may be reached. Deny is checked first and wins; a non-empty allow list refuses anything it does not name. Patterns are matched against the address as it will actually be sent, not only as you typed it, so a trailing dot on the host, `..` path segments, a default port written out, and percent-encoded path characters cannot be used to step around a rule. Writing a default port in a pattern (`https://example.com:443/*`) and omitting it are equivalent. Both are empty by default, which permits everything, exactly as before. They apply to the WebFetch call your AI makes, to the fetches token-goat performs itself (`fetch-image`, `gdrive-sections`), and to the headless browser behind `screenshot` (whose page sub-resources are checked too), including every redirect hop, so an allowed site cannot redirect the request on to a denied one.
|
|
62
66
|
|
|
63
67
|
**Where cached content lives, and who can read it.** Cached command output, fetched pages, MCP results, session state and the source index all sit under one data directory (`~/.local/share/token-goat` on Linux, `~/Library/Application Support/token-goat` on macOS, `%LOCALAPPDATA%\dfk-helper\token-goat` on Windows). On POSIX that directory is created owner-only (mode 0700), and an existing one is tightened on the next run, so other local users on a shared build host cannot read it. Windows uses inherited ACLs instead. Individual JSON blobs are additionally written 0600.
|
|
64
68
|
|
|
65
|
-
**Confining `symbol` to one project.** `token-goat symbol` is the one read command that answers from the machine-wide index (`global.db`) rather than the current project, so by default `symbol <name>` and `symbol --grep .` return matching symbols, bodies included, from every project ever indexed on the host. That is deliberate and useful on a personal machine: it is how you find a helper you wrote in another repo. On a shared build host, or under an agent you have confined to one directory, it is a read channel that the directory sandbox does not close, because the answer comes out of the index instead of the filesystem. Set `indexing.cross_project_symbols = false` (env `TOKEN_GOAT_CROSS_PROJECT_SYMBOLS`) and `symbol` only answers from the project it is run in. `--project` and `--file` pointing outside that project are refused rather than honored, so the setting cannot be stepped around from inside the confined process. Every other read command (`read`, `refs`, `callers`, `types`, `dead`, `find`, `semantic`) is already project-scoped and is unaffected.
|
|
69
|
+
**Confining `symbol` to one project.** `token-goat symbol` is the one read command that answers from the machine-wide index (`global.db`) rather than the current project, so by default `symbol <name>` and `symbol --grep .` return matching symbols, bodies included, from every project ever indexed on the host. That is deliberate and useful on a personal machine: it is how you find a helper you wrote in another repo. On a shared build host, or under an agent you have confined to one directory, it is a read channel that the directory sandbox does not close, because the answer comes out of the index instead of the filesystem. Set `indexing.cross_project_symbols = false` (env `TOKEN_GOAT_CROSS_PROJECT_SYMBOLS`) and `symbol` only answers from the project it is run in. `--project` and `--file` pointing outside that project are refused rather than honored, so the setting cannot be stepped around from inside the confined process. Every other read command (`read`, `refs`, `callers`, `types`, `dead`, `find`, `semantic`) is already project-scoped and is unaffected. Because the permissive setting is the default and says nothing about itself, `token-goat doctor` prints a `Security symbol scope` line stating which way it is currently set, and names the switch when lookups are unconfined.
|
|
66
70
|
|
|
67
71
|
**Redacting credentials this build has never heard of.** Before anything is written to disk or handed back to the model, token-goat redacts the credential shapes it recognises — 19 patterns covering AWS, GitHub, Slack, Stripe, OpenAI, Anthropic, Google, npm and Azure keys, private key blocks, JWTs, bearer and basic auth headers, presigned URL signatures, and credentials embedded in a URL. Two settings cover what a fixed list cannot. `redaction.custom_patterns` (env `TOKEN_GOAT_REDACTION_CUSTOM_PATTERNS`, one pattern per line) is a list of your own regular expressions, redacted as `[REDACTED:custom]` — for an in-house token prefix, an employee number, an internal account id. A pattern that does not compile is skipped and named by `token-goat doctor` rather than failing silently, because a redaction rule you believe is running and which is not is worse than none at all. `redaction.strict` (env `TOKEN_GOAT_REDACTION_STRICT`, off by default) additionally redacts long high-entropy strings that match nothing known: the shape of a credential nobody wrote a rule for. It is a heuristic, and it says so — it needs three of the four character classes and genuine randomness, which spares git SHAs and hex digests, but it will sometimes redact a base64 blob that was not a secret. Both are settings a per-project config file may not touch, so a checked-in `.token-goat.toml` cannot weaken redaction; an environment variable still can, which is what the `Security config overrides` line above exists to surface.
|
|
68
72
|
|