@growthagent/ci 0.4.0 → 0.5.0
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/cli.js +57 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -100,6 +100,7 @@ var FORBIDDEN_PATTERNS = [
|
|
|
100
100
|
{ pattern: /^\.github\//i, why: "workflow and CI configuration" },
|
|
101
101
|
{ pattern: /^\.git(\/|$)/i, why: "git internals" },
|
|
102
102
|
{ pattern: /(^|\/)\.env(\.|$)/i, why: "environment files" },
|
|
103
|
+
// except the examples below
|
|
103
104
|
{ pattern: /(^|\/)\.npmrc$|(^|\/)\.yarnrc(\.yml)?$|(^|\/)\.pypirc$/i, why: "registry credentials" },
|
|
104
105
|
{ pattern: /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|poetry\.lock|Gemfile\.lock|composer\.lock)$/i, why: "dependency lockfiles" },
|
|
105
106
|
{ pattern: /(^|\/)(Dockerfile|docker-compose\.ya?ml|Procfile)$/i, why: "build and deployment configuration" },
|
|
@@ -111,6 +112,44 @@ var FORBIDDEN_PATTERNS = [
|
|
|
111
112
|
{ pattern: /\.(pem|p12|pfx|key|keystore|jks)$/i, why: "key material" },
|
|
112
113
|
{ pattern: /(^|\/)node_modules\//i, why: "installed dependencies" }
|
|
113
114
|
];
|
|
115
|
+
var EXAMPLE_ENV = /(^|\/)\.env(\.[\w-]+)*\.(example|sample|template)$/i;
|
|
116
|
+
var CREDENTIAL_PREFIXES = [
|
|
117
|
+
"sk-",
|
|
118
|
+
"sk_live",
|
|
119
|
+
"sk_test",
|
|
120
|
+
"pk_live",
|
|
121
|
+
"rk_live",
|
|
122
|
+
"whsec_",
|
|
123
|
+
"phc_",
|
|
124
|
+
"ghp_",
|
|
125
|
+
"gho_",
|
|
126
|
+
"ghs_",
|
|
127
|
+
"github_pat_",
|
|
128
|
+
"AKIA",
|
|
129
|
+
"AIza",
|
|
130
|
+
"xox",
|
|
131
|
+
"eyJ",
|
|
132
|
+
"-----BEGIN"
|
|
133
|
+
];
|
|
134
|
+
function secretishEnvLine(line) {
|
|
135
|
+
const text = line.trim();
|
|
136
|
+
if (!text || text.startsWith("#")) return null;
|
|
137
|
+
const eq = text.indexOf("=");
|
|
138
|
+
if (eq < 0) return null;
|
|
139
|
+
const key = text.slice(0, eq).replace(/^export\s+/, "").trim();
|
|
140
|
+
const value = text.slice(eq + 1).trim().replace(/^["']|["']$/g, "").trim();
|
|
141
|
+
if (!value) return null;
|
|
142
|
+
if (/^https?:\/\//i.test(value)) return null;
|
|
143
|
+
for (const prefix of CREDENTIAL_PREFIXES) {
|
|
144
|
+
if (value.startsWith(prefix)) {
|
|
145
|
+
return `${key} carries what looks like a real credential`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (value.length >= 20 && /^[A-Za-z0-9_\-.]+$/.test(value)) {
|
|
149
|
+
return `${key} carries a ${value.length}-character opaque value, not a placeholder`;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
114
153
|
var SYMLINK_MODE = "120000";
|
|
115
154
|
var EXECUTABLE_MODE = "100755";
|
|
116
155
|
function parsePatch(patch) {
|
|
@@ -183,6 +222,7 @@ function forbiddenReason(filePath) {
|
|
|
183
222
|
return "an absolute path";
|
|
184
223
|
}
|
|
185
224
|
if (normalised.split("/").includes("..")) return "a parent-directory escape";
|
|
225
|
+
if (EXAMPLE_ENV.test(normalised)) return null;
|
|
186
226
|
for (const { pattern, why } of FORBIDDEN_PATTERNS) {
|
|
187
227
|
if (pattern.test(normalised)) return why;
|
|
188
228
|
}
|
|
@@ -210,6 +250,20 @@ function validatePatch(patch, limits = DEFAULT_PATCH_LIMITS) {
|
|
|
210
250
|
return { ok: false, reason: `the patch changes ${candidate}, which is ${why} \u2014 never modifiable by the agent` };
|
|
211
251
|
}
|
|
212
252
|
}
|
|
253
|
+
if (EXAMPLE_ENV.test(file.path)) {
|
|
254
|
+
if (file.addedLines > file.added.length) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
reason: `the patch adds ${file.addedLines} lines to ${file.path}, more than can be checked for credentials`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
for (const line of file.added) {
|
|
261
|
+
const why = secretishEnvLine(line);
|
|
262
|
+
if (why) {
|
|
263
|
+
return { ok: false, reason: `the patch writes a secret into ${file.path}: ${why}` };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
213
267
|
if (/(^|\/)package\.json$/i.test(file.path)) {
|
|
214
268
|
const why = manifestChangeReason(file);
|
|
215
269
|
if (why) {
|
|
@@ -537,8 +591,9 @@ async function implement() {
|
|
|
537
591
|
console.error("pi exited non-zero \u2014 no patch produced");
|
|
538
592
|
return 1;
|
|
539
593
|
}
|
|
540
|
-
sh("git", ["
|
|
541
|
-
sh("git", ["
|
|
594
|
+
const piTracked = sh("git", ["ls-files", "--", ".pi"], { allowFail: true });
|
|
595
|
+
if (piTracked) sh("git", ["checkout", "--", ".pi"], { allowFail: true });
|
|
596
|
+
sh("git", ["clean", "-fdq", ".pi"], { allowFail: true });
|
|
542
597
|
sh("git", ["add", "-A"]);
|
|
543
598
|
const patch = sh("git", ["diff", "--staged"]);
|
|
544
599
|
if (!patch.trim()) {
|