kritya 0.8.61-beta → 0.8.62-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/memory.js +9 -0
- package/dist/tools/document.js +19 -0
- package/dist/tools/notebook.js +10 -0
- package/dist/tools/secretScan.js +28 -0
- package/dist/tools/shell.js +9 -1
- package/package.json +1 -1
package/dist/agent/memory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { scanForSecrets } from "../tools/secretScan.js";
|
|
3
4
|
const MEMORY_FILE = "KRITYA.md";
|
|
4
5
|
const AUTO_HEADING = "## Learned by kritya (auto-updated; edit or delete freely)";
|
|
5
6
|
const MAX_FACTS = 20;
|
|
@@ -132,6 +133,14 @@ export function mergeProjectMemory(workspace, newFacts) {
|
|
|
132
133
|
for (const fact of newFacts) {
|
|
133
134
|
if (seen.has(fact.toLowerCase()))
|
|
134
135
|
continue;
|
|
136
|
+
// Write-time secret guard: facts are extracted from session transcripts,
|
|
137
|
+
// which can include credentials the model saw in tool output, and
|
|
138
|
+
// KRITYA.md is a plain workspace file that is read back into future
|
|
139
|
+
// prompts. A fact that looks like it contains a real secret is dropped
|
|
140
|
+
// rather than persisted — silently, since there is no user-facing write
|
|
141
|
+
// to block here, and the fact's value is the secret itself.
|
|
142
|
+
if (scanForSecrets(fact).length > 0)
|
|
143
|
+
continue;
|
|
135
144
|
seen.add(fact.toLowerCase());
|
|
136
145
|
added.push(fact);
|
|
137
146
|
}
|
package/dist/tools/document.js
CHANGED
|
@@ -6,6 +6,7 @@ import { readDocx, writeDocx } from "./document/docx.js";
|
|
|
6
6
|
import { readXlsx, writeXlsx, editXlsx } from "./document/xlsx.js";
|
|
7
7
|
import { readPptx, writePptx } from "./document/pptx.js";
|
|
8
8
|
import { readPdf, writePdf, editPdf } from "./document/pdf.js";
|
|
9
|
+
import { formatSecretWarning, scanForSecrets } from "./secretScan.js";
|
|
9
10
|
const READABLE_EXTENSIONS = [".docx", ".xlsx", ".pptx", ".pdf"];
|
|
10
11
|
/**
|
|
11
12
|
* Bound the raw file read before it's dispatched to any format-specific
|
|
@@ -142,6 +143,14 @@ export const writeDocumentTool = {
|
|
|
142
143
|
const abs = resolveSafe(ctx.workspace, relPath);
|
|
143
144
|
const ext = path.extname(abs).toLowerCase();
|
|
144
145
|
const content = extractContent(args);
|
|
146
|
+
// Same write-time secret guard as write_file: the blocks/sheets/slides
|
|
147
|
+
// payload is model-authored text, and a generated .docx/.xlsx is still
|
|
148
|
+
// a file a real key shouldn't be baked into. The payload is structured,
|
|
149
|
+
// so scan its JSON form — every user-visible string is in there.
|
|
150
|
+
const secrets = scanForSecrets(JSON.stringify(content));
|
|
151
|
+
if (secrets.length > 0) {
|
|
152
|
+
throw new Error(formatSecretWarning(secrets, relPath));
|
|
153
|
+
}
|
|
145
154
|
let buf;
|
|
146
155
|
switch (ext) {
|
|
147
156
|
case ".docx":
|
|
@@ -232,6 +241,16 @@ export const editSpreadsheetTool = {
|
|
|
232
241
|
const edits = (args.edits ?? []);
|
|
233
242
|
if (edits.length === 0)
|
|
234
243
|
throw new Error("edit_spreadsheet: `edits` must be non-empty");
|
|
244
|
+
// Write-time secret guard, same as write_file: a string cell value is
|
|
245
|
+
// model-authored content being persisted into the workbook.
|
|
246
|
+
for (const edit of edits) {
|
|
247
|
+
if (typeof edit.value !== "string")
|
|
248
|
+
continue;
|
|
249
|
+
const secrets = scanForSecrets(edit.value);
|
|
250
|
+
if (secrets.length > 0) {
|
|
251
|
+
throw new Error(formatSecretWarning(secrets, relPath));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
235
254
|
const buf = await fs.readFile(abs);
|
|
236
255
|
const { buf: out, applied } = await editXlsx(buf, edits);
|
|
237
256
|
ctx.undo?.snapshot(abs, relPath);
|
package/dist/tools/notebook.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { writeFileAtomic } from "../atomicWrite.js";
|
|
3
3
|
import { resolveSafe, truncateResult } from "./common.js";
|
|
4
|
+
import { formatSecretWarning, scanForSecrets } from "./secretScan.js";
|
|
4
5
|
/** nbformat stores text as either a single string or an array of lines. */
|
|
5
6
|
function joinSource(source) {
|
|
6
7
|
if (Array.isArray(source))
|
|
@@ -170,6 +171,15 @@ export const editNotebookTool = {
|
|
|
170
171
|
}
|
|
171
172
|
const nb = await loadNotebook(abs);
|
|
172
173
|
const cells = nb.cells;
|
|
174
|
+
// Same write-time secret guard as write_file/edit_file: cell source is
|
|
175
|
+
// model-authored content persisted to disk, and a notebook is an
|
|
176
|
+
// ordinary file a key can hide in just as easily as a .ts file.
|
|
177
|
+
if (op !== "delete" && args.source !== undefined) {
|
|
178
|
+
const secrets = scanForSecrets(String(args.source));
|
|
179
|
+
if (secrets.length > 0) {
|
|
180
|
+
throw new Error(formatSecretWarning(secrets, relPath));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
173
183
|
switch (op) {
|
|
174
184
|
case "replace": {
|
|
175
185
|
if (index >= cells.length) {
|
package/dist/tools/secretScan.js
CHANGED
|
@@ -148,3 +148,31 @@ export function formatSecretWarning(matches, path) {
|
|
|
148
148
|
`match common key formats or lower its entropy — for example, using an obvious placeholder ` +
|
|
149
149
|
`like "sk-ant-EXAMPLE...".`);
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Shell commands are the one write path the file tools can't see: a command
|
|
153
|
+
* like `echo '<key>' > notes.txt` or a heredoc puts a literal secret on disk
|
|
154
|
+
* without going through write_file/edit_file. The command string itself is
|
|
155
|
+
* the only place to catch that — but a secret merely *appearing* in a
|
|
156
|
+
* command (a curl Authorization header, say) doesn't put it in a file, so
|
|
157
|
+
* this only fires when the command also has a file-writing construct: an
|
|
158
|
+
* output redirect to a real target (not `>&` fd duplication or a `>=`
|
|
159
|
+
* comparison), `tee`, or `dd of=`. Heredocs are covered through the
|
|
160
|
+
* redirect they almost always pair with (`cat > f <<EOF`); a heredoc fed
|
|
161
|
+
* to an interpreter that then writes the secret itself is out of reach for
|
|
162
|
+
* a string scan and remains a known gap, like novel key formats.
|
|
163
|
+
*/
|
|
164
|
+
const SHELL_WRITE_RE = />{1,2}\s*(?![&=>])|\btee\b|\bdd\b[^|;&]*\bof=/;
|
|
165
|
+
export function scanCommandForSecretWrite(command) {
|
|
166
|
+
if (!SHELL_WRITE_RE.test(command))
|
|
167
|
+
return [];
|
|
168
|
+
return scanForSecrets(command);
|
|
169
|
+
}
|
|
170
|
+
export function formatShellSecretWarning(matches) {
|
|
171
|
+
const lines = matches.map((m) => ` - ${m.kind}: ${m.snippet}`);
|
|
172
|
+
return (`Blocked shell command: it writes to a file and contains what looks like real secret(s):\n` +
|
|
173
|
+
lines.join("\n") +
|
|
174
|
+
`\n\nEmbedding a literal secret in a shell command writes it to disk and shell history. ` +
|
|
175
|
+
`Reference the value from the environment instead (e.g. $API_KEY), or if this is ` +
|
|
176
|
+
`genuinely not a secret (e.g. a test fixture), rewrite it so it doesn't match common ` +
|
|
177
|
+
`key formats — for example, using an obvious placeholder like "sk-ant-EXAMPLE...".`);
|
|
178
|
+
}
|
package/dist/tools/shell.js
CHANGED
|
@@ -4,7 +4,7 @@ import { gitDiffStat } from "../git/git.js";
|
|
|
4
4
|
import { backgroundManager } from "../shell/background.js";
|
|
5
5
|
import { buildSandboxedCommand, networkBlockedHint, requiresSandbox, sandboxUnavailableReason, shouldSandbox, } from "../shell/sandbox.js";
|
|
6
6
|
import { commandTouchesSensitivePath, truncateTail } from "./common.js";
|
|
7
|
-
import { redactSecrets } from "./secretScan.js";
|
|
7
|
+
import { redactSecrets, scanCommandForSecretWrite, formatShellSecretWarning, } from "./secretScan.js";
|
|
8
8
|
const DEFAULT_TIMEOUT_S = 120;
|
|
9
9
|
const MAX_TIMEOUT_S = 600;
|
|
10
10
|
/** git subcommands that rewrite the working tree, index, or history. */
|
|
@@ -63,6 +63,14 @@ export const shellTool = {
|
|
|
63
63
|
if (sensitivePath) {
|
|
64
64
|
throw new Error(`Command references "${sensitivePath}", which looks like a secret file, and is blocked from tool access`);
|
|
65
65
|
}
|
|
66
|
+
// A command can also *create* the secret leak: a literal key in the
|
|
67
|
+
// command string plus a redirect/tee/dd target writes it to a file
|
|
68
|
+
// without going through the scanned file tools. Block that here, for
|
|
69
|
+
// background commands too — this check sits above the background branch.
|
|
70
|
+
const secretWrite = scanCommandForSecretWrite(command);
|
|
71
|
+
if (secretWrite.length > 0) {
|
|
72
|
+
throw new Error(formatShellSecretWarning(secretWrite));
|
|
73
|
+
}
|
|
66
74
|
if (args.background) {
|
|
67
75
|
const { id } = backgroundManager.start(command, ctx.workspace, ctx.sandboxMode);
|
|
68
76
|
// Echoing the command back can leak a credential embedded in it (e.g. a
|