jules-orchestrator-kit 0.6.0 → 0.6.2
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/.env.example +7 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/scripts/jules-self-audit.mjs +31 -11
- package/scripts/utils.mjs +65 -23
package/.env.example
CHANGED
|
@@ -20,6 +20,13 @@ JULES_REPO=owner/repo
|
|
|
20
20
|
# Enable Dry-Run mode to simulate payload dispatch without making API calls
|
|
21
21
|
# JULES_DRY_RUN=false
|
|
22
22
|
|
|
23
|
+
# --- Budget & Security Gatekeeper ---
|
|
24
|
+
# Daily max session limit for autonomous dispatches (Default: 300)
|
|
25
|
+
# JULES_DAILY_BUDGET=300
|
|
26
|
+
|
|
27
|
+
# Allow modifications to command-defining files (package.json, Cargo.toml, etc.) in PRs (Default: false)
|
|
28
|
+
# JULES_ALLOW_COMMAND_FILE_CHANGES=false
|
|
29
|
+
|
|
23
30
|
# --- Execution Scope & Git ---
|
|
24
31
|
# Base Branch for PR Audits & Merge-Base Calculations (Default: main)
|
|
25
32
|
BASE_BRANCH=main
|
package/README.md
CHANGED
|
@@ -162,6 +162,8 @@ All commands are registered in `package.json` and can be run via `npm run <comma
|
|
|
162
162
|
| `JULES_REPO` | Target GitHub Repository (Format: `owner/repo`) |
|
|
163
163
|
| `JULES_REPOLESS` | Set to `true` or `1` to run in repoless/serverless mode |
|
|
164
164
|
| `JULES_DRY_RUN` | Set to `true` or `1` to simulate dispatching without making API calls |
|
|
165
|
+
| `JULES_DAILY_BUDGET` | Daily max session budget for autonomous dispatches (Default: `300`) |
|
|
166
|
+
| `JULES_ALLOW_COMMAND_FILE_CHANGES` | Set to `true` to allow PR changes to command-defining files like `package.json` (Default: `false`) |
|
|
165
167
|
| `BASE_BRANCH` | Base branch for PR Audits & Merge-Base calculations (Default: `main`) |
|
|
166
168
|
| `GITHUB_HEAD_REF` | PR Head Branch (Used dynamically by CI during OODA repair) |
|
|
167
169
|
| `JULES_PROJECT_ROOT` | Root directory of the project (Auto-assigned during swarm executions) |
|
|
@@ -176,6 +178,10 @@ All commands are registered in `package.json` and can be run via `npm run <comma
|
|
|
176
178
|
| `GITHUB_STEP_SUMMARY` | GitHub Actions Step Summary File Path |
|
|
177
179
|
| `NO_COLOR` | Set to `true` to disable ANSI color output |
|
|
178
180
|
|
|
181
|
+
> [!NOTE]
|
|
182
|
+
> **Scope Enforcement via `allow_paths`**
|
|
183
|
+
> In `.agent/jules.yml`, defining `allow_paths` acts as a strict allowlist (deny-by-default). When non-empty, any file modified outside `allow_paths` will trigger a security violation (Exit Code 3). `forbidden_paths` always take absolute precedence and cannot be overridden.
|
|
184
|
+
|
|
179
185
|
### Exit Codes
|
|
180
186
|
The Gatekeeper (`jules-self-audit.mjs` and related scripts) uses standard exit codes to signal status to CI systems.
|
|
181
187
|
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import crypto from "node:crypto";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { resolveWorkspaceExecutionBoundary } from "./command-resolver.mjs";
|
|
8
|
-
import { log, logToHistory, redactSecrets } from "./utils.mjs";
|
|
8
|
+
import { log, logToHistory, redactSecrets, hasHighConfidenceSecret, hasLowConfidenceSecret } from "./utils.mjs";
|
|
9
9
|
|
|
10
10
|
function runGitCommand(args, ignoreError = false) {
|
|
11
11
|
try {
|
|
@@ -261,24 +261,37 @@ export function runSelfAudit() {
|
|
|
261
261
|
}
|
|
262
262
|
log.success("Restricted File Boundary Check: PASSED");
|
|
263
263
|
|
|
264
|
-
log.info("Analyzing Diff Payload Size & Content...");
|
|
265
|
-
const
|
|
266
|
-
|
|
264
|
+
log.info("Analyzing Code Diff Payload Size & Content...");
|
|
265
|
+
const codeDiffPayload = changedCodeFiles.length > 0
|
|
266
|
+
? runGitCommand(["diff", `${mergeBase}...HEAD`, "--", ...changedCodeFiles], true)
|
|
267
|
+
: "";
|
|
268
|
+
|
|
269
|
+
const diffBytes = Buffer.byteLength(codeDiffPayload, "utf8");
|
|
267
270
|
const maxDiffBytes = 75 * 1024; // 75 KB
|
|
268
271
|
|
|
269
272
|
if (diffBytes > maxDiffBytes) {
|
|
270
|
-
const err = new Error(`DIFF PAYLOAD TOO LARGE: ${diffBytes} bytes exceeds ${maxDiffBytes} bytes. Split the task.`);
|
|
273
|
+
const err = new Error(`DIFF PAYLOAD TOO LARGE: ${diffBytes} bytes exceeds ${maxDiffBytes} bytes limit for code files. Split the task.`);
|
|
271
274
|
err.code = 5;
|
|
272
275
|
throw err;
|
|
273
276
|
}
|
|
274
277
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
const fullDiffPayload = runGitCommand(["diff", `${mergeBase}...HEAD`], true);
|
|
279
|
+
const addedLines = fullDiffPayload
|
|
280
|
+
.split("\n")
|
|
281
|
+
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
|
|
282
|
+
.map((line) => line.slice(1))
|
|
283
|
+
.join("\n");
|
|
284
|
+
|
|
285
|
+
if (hasHighConfidenceSecret(addedLines)) {
|
|
286
|
+
const err = new Error("SECRET LEAK PREVENTED: High-confidence secret token or private key detected in added diff lines. Aborting.");
|
|
278
287
|
err.code = 6;
|
|
279
288
|
throw err;
|
|
280
289
|
}
|
|
281
|
-
|
|
290
|
+
|
|
291
|
+
if (hasLowConfidenceSecret(addedLines)) {
|
|
292
|
+
log.warn("SECURITY WARNING: Low-confidence secret or test key pattern detected in added diff lines.");
|
|
293
|
+
}
|
|
294
|
+
log.success(`Diff Inspection Check: PASSED (${diffBytes} bytes code diff, No Active High-Confidence Secrets)`);
|
|
282
295
|
|
|
283
296
|
log.info("Resolving Dynamic Verification Suite from Trusted Base Branch...");
|
|
284
297
|
|
|
@@ -382,7 +395,7 @@ export function runSelfAudit() {
|
|
|
382
395
|
}
|
|
383
396
|
}
|
|
384
397
|
} catch (e) {
|
|
385
|
-
//
|
|
398
|
+
// Ignore errors here
|
|
386
399
|
}
|
|
387
400
|
|
|
388
401
|
if (oodaRetries < 3) {
|
|
@@ -415,6 +428,13 @@ export function runSelfAudit() {
|
|
|
415
428
|
throw err;
|
|
416
429
|
}
|
|
417
430
|
|
|
431
|
+
try {
|
|
432
|
+
const oodaStateFile = path.resolve(process.cwd(), ".agent/state/ooda.json");
|
|
433
|
+
if (fs.existsSync(oodaStateFile)) {
|
|
434
|
+
fs.rmSync(oodaStateFile, { force: true });
|
|
435
|
+
}
|
|
436
|
+
} catch (_) {}
|
|
437
|
+
|
|
418
438
|
log.success("🎉 JULES PR SELF-AUDIT PASSED SUCCESSFULLY!");
|
|
419
439
|
}
|
|
420
440
|
|
|
@@ -486,7 +506,7 @@ export function runPreflightSandbox() {
|
|
|
486
506
|
fs.rmSync(tempArchive, { force: true });
|
|
487
507
|
}
|
|
488
508
|
} catch (e) {
|
|
489
|
-
//
|
|
509
|
+
// Ignore cleanup errors
|
|
490
510
|
}
|
|
491
511
|
}
|
|
492
512
|
}
|
package/scripts/utils.mjs
CHANGED
|
@@ -106,6 +106,42 @@ export function calculateShannonEntropy(str) {
|
|
|
106
106
|
}, 0);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
export const HIGH_CONFIDENCE_PATTERNS = [
|
|
110
|
+
/\bgh[pousr]_[a-zA-Z0-9]{36,}\b/g,
|
|
111
|
+
/\bgithub_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}\b/g,
|
|
112
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
113
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
114
|
+
/\bnpm_[a-zA-Z0-9]{36}\b/g,
|
|
115
|
+
/\bsk_live_[0-9a-zA-Z]{24,}\b/g,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
export const LOW_CONFIDENCE_PATTERNS = [
|
|
119
|
+
/\bAIza[0-9A-Za-z_-]{30,40}\b/g,
|
|
120
|
+
/\bya29\.[a-zA-Z0-9_\-]{20,}\b/g,
|
|
121
|
+
/\bBearer\s+[a-zA-Z0-9\-\._~+\/]+=*/g,
|
|
122
|
+
/\bsk-(?:ant-api03-|proj-|svcacct-)[a-zA-Z0-9\-\_]{32,}\b/g,
|
|
123
|
+
/\bxox[baprs]-[a-zA-Z0-9\-]{10,}\b/g,
|
|
124
|
+
/\beyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\b/g,
|
|
125
|
+
/\b(?:rk|pk)_(?:live|test)_[0-9a-zA-Z]{24,}\b/g,
|
|
126
|
+
/\bsk_test_[0-9a-zA-Z]{24,}\b/g,
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
export function hasHighConfidenceSecret(text) {
|
|
130
|
+
if (!text) return false;
|
|
131
|
+
return HIGH_CONFIDENCE_PATTERNS.some((pat) => {
|
|
132
|
+
pat.lastIndex = 0;
|
|
133
|
+
return pat.test(text);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function hasLowConfidenceSecret(text) {
|
|
138
|
+
if (!text) return false;
|
|
139
|
+
return LOW_CONFIDENCE_PATTERNS.some((pat) => {
|
|
140
|
+
pat.lastIndex = 0;
|
|
141
|
+
return pat.test(text);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
109
145
|
export function redactSecrets(text) {
|
|
110
146
|
if (!text) return "";
|
|
111
147
|
let sanitized = text;
|
|
@@ -122,21 +158,8 @@ export function redactSecrets(text) {
|
|
|
122
158
|
}
|
|
123
159
|
}
|
|
124
160
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
/\bgithub_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}\b/g,
|
|
128
|
-
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
129
|
-
/\bAIza[0-9A-Za-z_-]{30,40}\b/g,
|
|
130
|
-
/\bya29\.[a-zA-Z0-9_\-]{20,}\b/g,
|
|
131
|
-
/\bBearer\s+[a-zA-Z0-9\-\._~+\/]+=*/g,
|
|
132
|
-
/\bsk-(?:ant-api03-|proj-|svcacct-)[a-zA-Z0-9\-\_]{32,}\b/g,
|
|
133
|
-
/\bxox[baprs]-[a-zA-Z0-9\-]{10,}\b/g,
|
|
134
|
-
/\beyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\b/g,
|
|
135
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
136
|
-
/\bnpm_[a-zA-Z0-9]{36}\b/g,
|
|
137
|
-
/\b(?:sk|rk|pk)_(?:live|test)_[0-9a-zA-Z]{24,}\b/g,
|
|
138
|
-
];
|
|
139
|
-
for (const pat of patterns) {
|
|
161
|
+
const allPatterns = [...HIGH_CONFIDENCE_PATTERNS, ...LOW_CONFIDENCE_PATTERNS];
|
|
162
|
+
for (const pat of allPatterns) {
|
|
140
163
|
sanitized = sanitized.replace(pat, "[REDACTED_BY_SECURITY_GATE]");
|
|
141
164
|
}
|
|
142
165
|
return sanitized;
|
|
@@ -173,7 +196,7 @@ export function checkDailyBudget(maxSessions = 300) {
|
|
|
173
196
|
if (
|
|
174
197
|
entry.timestamp &&
|
|
175
198
|
entry.timestamp.startsWith(dateStr) &&
|
|
176
|
-
(entry.event === "
|
|
199
|
+
(entry.event === "budget_reserved" || !entry.event)
|
|
177
200
|
) {
|
|
178
201
|
usedToday++;
|
|
179
202
|
}
|
|
@@ -185,24 +208,43 @@ export function checkDailyBudget(maxSessions = 300) {
|
|
|
185
208
|
return { ok: usedToday < maxSessions, used: usedToday, budget: maxSessions };
|
|
186
209
|
}
|
|
187
210
|
|
|
211
|
+
function sleepSync(ms) {
|
|
212
|
+
try {
|
|
213
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
214
|
+
} catch (_) {}
|
|
215
|
+
}
|
|
216
|
+
|
|
188
217
|
export function reserveDailyBudget(maxSessions = 300, taskKey = "") {
|
|
189
218
|
const stateDir = path.resolve(process.env.JULES_PROJECT_ROOT || process.cwd(), ".agent/state");
|
|
190
219
|
ensureDir(stateDir);
|
|
191
220
|
|
|
192
221
|
const lockFile = path.join(stateDir, "budget.lock");
|
|
193
222
|
let fd;
|
|
223
|
+
const maxLockAttempts = 10;
|
|
194
224
|
|
|
195
|
-
|
|
196
|
-
fd = fs.openSync(lockFile, "wx");
|
|
197
|
-
} catch {
|
|
198
|
-
// If lock fails, wait 50ms and retry once before returning locked state
|
|
225
|
+
for (let attempt = 0; attempt < maxLockAttempts; attempt++) {
|
|
199
226
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
try {
|
|
228
|
+
const stat = fs.statSync(lockFile);
|
|
229
|
+
if (Date.now() - stat.mtimeMs > 30000) {
|
|
230
|
+
fs.rmSync(lockFile, { force: true });
|
|
231
|
+
}
|
|
232
|
+
} catch (_) {}
|
|
233
|
+
|
|
234
|
+
fd = fs.openSync(lockFile, "wx");
|
|
235
|
+
break;
|
|
236
|
+
} catch (err) {
|
|
237
|
+
if (err.code !== "EEXIST") throw err;
|
|
238
|
+
if (attempt < maxLockAttempts - 1) {
|
|
239
|
+
sleepSync(Math.floor(50 + Math.random() * 50));
|
|
240
|
+
}
|
|
203
241
|
}
|
|
204
242
|
}
|
|
205
243
|
|
|
244
|
+
if (fd === undefined) {
|
|
245
|
+
return { ok: false, reason: "locked", used: null, budget: maxSessions };
|
|
246
|
+
}
|
|
247
|
+
|
|
206
248
|
try {
|
|
207
249
|
const check = checkDailyBudget(maxSessions);
|
|
208
250
|
if (!check.ok) {
|