@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.
Files changed (2) hide show
  1. package/dist/cli.js +57 -2
  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", ["checkout", "--", ".pi"], { allowFail: true });
541
- sh("git", ["clean", "-fd", ".pi"], { allowFail: true });
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()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthagent/ci",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {