@lazyingart/agintiflow 0.20.73 → 0.20.74
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/package.json +1 -1
- package/scripts/smoke-coding-tools.js +51 -0
- package/src/behavior-contract.js +1 -0
- package/src/redaction.js +5 -0
- package/src/workspace-tools.js +45 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.74",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Low-cost, project-aware Web and CLI agents with DeepSeek/Venice/OpenAI routing, visible tool calls, durable sessions, scouts, AAPS, SCS, and guarded local execution.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -530,6 +530,54 @@ try {
|
|
|
530
530
|
});
|
|
531
531
|
assert(envRun.events.some((event) => event.type === "tool.blocked"), ".env guardrail did not emit tool.blocked");
|
|
532
532
|
|
|
533
|
+
const secretContentResult = await executeWorkspaceTool(
|
|
534
|
+
"write_file",
|
|
535
|
+
{
|
|
536
|
+
path: "notes/secret-leak-report.md",
|
|
537
|
+
content: "Content attempted: DEMO_SECRET_TOKEN=aginti_fake_do_not_use\n",
|
|
538
|
+
mode: "create",
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
commandCwd: workspace,
|
|
542
|
+
allowFileTools: true,
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
assert(secretContentResult.blocked && secretContentResult.category === "workspace-content", "write_file secret-like content was not blocked");
|
|
546
|
+
await fs
|
|
547
|
+
.access(path.join(workspace, "notes/secret-leak-report.md"))
|
|
548
|
+
.then(() => {
|
|
549
|
+
throw new Error("secret-like report content was written despite content guardrails");
|
|
550
|
+
})
|
|
551
|
+
.catch((error) => {
|
|
552
|
+
if (error.code !== "ENOENT") throw error;
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const redactedContentResult = await executeWorkspaceTool(
|
|
556
|
+
"write_file",
|
|
557
|
+
{
|
|
558
|
+
path: "notes/redacted-report.md",
|
|
559
|
+
content: "Content attempted: DEMO_SECRET_TOKEN=[REDACTED]\n",
|
|
560
|
+
mode: "create",
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
commandCwd: workspace,
|
|
564
|
+
allowFileTools: true,
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
assert(redactedContentResult.ok, "write_file should allow already-redacted secret placeholders");
|
|
568
|
+
|
|
569
|
+
const secretPatchResult = await executeWorkspaceTool(
|
|
570
|
+
"apply_patch",
|
|
571
|
+
{
|
|
572
|
+
patch: ["*** Begin Patch", "*** Add File: notes/secret-patch.md", "+DEMO_SECRET_TOKEN=aginti_fake_do_not_use", "*** End Patch"].join("\n"),
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
commandCwd: workspace,
|
|
576
|
+
allowFileTools: true,
|
|
577
|
+
}
|
|
578
|
+
);
|
|
579
|
+
assert(secretPatchResult.blocked && secretPatchResult.category === "workspace-content", "apply_patch secret-like additions were not blocked");
|
|
580
|
+
|
|
533
581
|
const outsideRun = await runMock("Create file: ../outside-workspace.txt with blocked content.", "coding-block-outside");
|
|
534
582
|
await fs
|
|
535
583
|
.access(path.join(tempRoot, "outside-workspace.txt"))
|
|
@@ -576,6 +624,9 @@ try {
|
|
|
576
624
|
"patch_guardrail",
|
|
577
625
|
"patch_move_no_overwrite",
|
|
578
626
|
"block_env",
|
|
627
|
+
"block_secret_write_content",
|
|
628
|
+
"allow_redacted_write_content",
|
|
629
|
+
"block_secret_patch_content",
|
|
579
630
|
"block_outside",
|
|
580
631
|
],
|
|
581
632
|
},
|
package/src/behavior-contract.js
CHANGED
|
@@ -69,6 +69,7 @@ export function formatBehaviorContractForPrompt({ mode = "runtime" } = {}) {
|
|
|
69
69
|
"Make surgical edits: no drive-by refactors, unrelated formatting churn, or deletion of code you did not need to touch.",
|
|
70
70
|
"Define or infer concrete success criteria for non-trivial work, then run focused checks or state why checks are unavailable.",
|
|
71
71
|
"Respect the permission contract: if a tool is blocked or returns permissionAdvice, stop and present the exact suggestedCommand/approval path instead of retrying variants or inventing CLI flags.",
|
|
72
|
+
"Protect secrets aggressively: never repeat token/key/password/secret values from prompts, files, tool output, plans, final answers, reports, diffs, or artifacts. Redact the value as [REDACTED] and use dedicated key storage such as `aginti keys set` when credentials are needed.",
|
|
72
73
|
"Keep artifacts durable and discoverable with descriptive non-conflicting names; never overwrite unless the user clearly asked.",
|
|
73
74
|
"When reporting shell, language, runtime, build, or test results, name the actual environment used (host vs Docker, relevant interpreter/tool path/version when it matters). Do not claim compatibility across untested runtimes, hosts, containers, or language versions; state the caveat or run an explicit check.",
|
|
74
75
|
"Do not self-invoke AgInTiFlow with npx/npm exec or nested aginti commands from inside the agent shell; it can resolve stale project packages or create recursive sessions. Use current runtime evidence, project/session files, or ask for a host-side diagnostic instead.",
|
package/src/redaction.js
CHANGED
|
@@ -19,6 +19,11 @@ export function redactSensitiveText(value) {
|
|
|
19
19
|
return text;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function hasSensitiveText(value) {
|
|
23
|
+
const text = String(value ?? "");
|
|
24
|
+
return redactSensitiveText(text) !== text;
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
export function redactValue(value) {
|
|
23
28
|
if (typeof value === "string") return redactSensitiveText(value);
|
|
24
29
|
if (Array.isArray(value)) return value.map((item) => redactValue(item));
|
package/src/workspace-tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { redactSensitiveText } from "./redaction.js";
|
|
4
|
+
import { hasSensitiveText, redactSensitiveText } from "./redaction.js";
|
|
5
5
|
|
|
6
6
|
export const WORKSPACE_TOOL_NAMES = ["inspect_project", "list_files", "read_file", "search_files", "write_file", "apply_patch"];
|
|
7
7
|
export const WORKSPACE_WRITE_TOOL_NAMES = ["write_file", "apply_patch"];
|
|
@@ -188,6 +188,37 @@ function pathPolicy(toolName, relativePath) {
|
|
|
188
188
|
return { allowed: true };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
function secretContentPolicy(content) {
|
|
192
|
+
if (!hasSensitiveText(content)) return { allowed: true };
|
|
193
|
+
return {
|
|
194
|
+
allowed: false,
|
|
195
|
+
reason:
|
|
196
|
+
"Write content appears to contain token-like secret text. Redact secret values as [REDACTED] or use dedicated key storage such as `aginti keys set`; do not write credentials into workspace files.",
|
|
197
|
+
category: "workspace-content",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function writeContentPolicy(toolName, args, operations = null) {
|
|
202
|
+
if (toolName === "write_file") return secretContentPolicy(args.content || "");
|
|
203
|
+
if (toolName !== "apply_patch") return { allowed: true };
|
|
204
|
+
|
|
205
|
+
if (typeof args.patch === "string" && args.patch.trim()) {
|
|
206
|
+
for (const operation of operations || []) {
|
|
207
|
+
if (operation.type === "add") {
|
|
208
|
+
const policy = secretContentPolicy(operation.content || "");
|
|
209
|
+
if (!policy.allowed) return policy;
|
|
210
|
+
}
|
|
211
|
+
for (const hunk of operation.hunks || []) {
|
|
212
|
+
const policy = secretContentPolicy(hunk.replace || "");
|
|
213
|
+
if (!policy.allowed) return policy;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { allowed: true };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return secretContentPolicy(args.replace ?? "");
|
|
220
|
+
}
|
|
221
|
+
|
|
191
222
|
export function checkWorkspaceToolUse(toolName, args, config) {
|
|
192
223
|
if (!WORKSPACE_TOOL_NAMES.includes(toolName)) return { allowed: true };
|
|
193
224
|
if (!config.allowFileTools) {
|
|
@@ -196,17 +227,26 @@ export function checkWorkspaceToolUse(toolName, args, config) {
|
|
|
196
227
|
|
|
197
228
|
try {
|
|
198
229
|
if (toolName === "apply_patch" && typeof args.patch === "string" && args.patch.trim()) {
|
|
199
|
-
|
|
230
|
+
const operations = parsePatchDocument(args.patch);
|
|
231
|
+
for (const operation of operations) {
|
|
200
232
|
for (const candidate of [operation.path, operation.newPath].filter(Boolean)) {
|
|
201
233
|
const target = resolveWorkspacePath(config, candidate);
|
|
202
234
|
const policy = pathPolicy(toolName, target.relativePath);
|
|
203
235
|
if (!policy.allowed) return policy;
|
|
204
236
|
}
|
|
205
237
|
}
|
|
238
|
+
const contentPolicy = writeContentPolicy(toolName, args, operations);
|
|
239
|
+
if (!contentPolicy.allowed) return contentPolicy;
|
|
206
240
|
return { allowed: true };
|
|
207
241
|
}
|
|
208
242
|
const target = resolveWorkspacePath(config, args.path || ".");
|
|
209
|
-
|
|
243
|
+
const policy = pathPolicy(toolName, target.relativePath);
|
|
244
|
+
if (!policy.allowed) return policy;
|
|
245
|
+
if (WORKSPACE_WRITE_TOOL_NAMES.includes(toolName)) {
|
|
246
|
+
const contentPolicy = writeContentPolicy(toolName, args);
|
|
247
|
+
if (!contentPolicy.allowed) return contentPolicy;
|
|
248
|
+
}
|
|
249
|
+
return { allowed: true };
|
|
210
250
|
} catch (error) {
|
|
211
251
|
return {
|
|
212
252
|
allowed: false,
|
|
@@ -591,6 +631,8 @@ async function writeChange(target, nextContent, action, details = {}) {
|
|
|
591
631
|
if (Buffer.byteLength(content, "utf8") > MAX_WRITE_BYTES) {
|
|
592
632
|
throw new Error(`Write is too large for safe workspace tools: ${target.relativePath}`);
|
|
593
633
|
}
|
|
634
|
+
const contentPolicy = secretContentPolicy(content);
|
|
635
|
+
if (!contentPolicy.allowed) throw new Error(contentPolicy.reason);
|
|
594
636
|
|
|
595
637
|
await fs.mkdir(path.dirname(target.absolutePath), { recursive: true });
|
|
596
638
|
let beforeText = "";
|