blastproof 0.14.0 → 0.15.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/README.md CHANGED
@@ -145,6 +145,8 @@ The agent is **forbidden from inventing values** — one it types must come from
145
145
 
146
146
  The rule used to live only in the prompt, and a prompt instructs rather than enforces. Run against a real model, `fill the note field` did not fail — the agent made a value up, filled it, and the step **passed**, producing "This is a test note." on two runs and "This is a new note" on a third. A test going green over a value nobody wrote, differing between runs, is worse than a failure.
147
147
 
148
+ A placeholder counts as a source **only when the step names that variable**. `fill the password field with {{env.TEST_PASSWORD}}` works; `fill the password field` does not become valid because the agent supplies `{{env.SOMETHING}}` itself. An agent cannot know the name of a variable nobody showed it, so one it produces is a guess — and a guessed variable would put a live credential into a field your test never pointed one at, in output that cannot redact a secret it was never told about.
149
+
148
150
  Two limits worth knowing. A value the page shows in one format and the field wants in another — `1234` in the step, `1,234.00` in the box — is refused, and the fix is to write the value the way it is typed. And a very short value (`3`) appears somewhere in almost any page, so it will pass; this closes fabricated content, not every fabricated character.
149
151
 
150
152
  `run` also warns about it first — the same rule caught earlier, from the test file, on every path, before launching a browser or asking for a key:
package/dist/cli.js CHANGED
@@ -509,7 +509,10 @@ async function performAction(page, action, ctx) {
509
509
  var COMMIT_ACTIONS = /* @__PURE__ */ new Set(["click", "press"]);
510
510
  var COMMIT_KEYS = /* @__PURE__ */ new Set(["Enter", "NumpadEnter", " ", "Space", "Spacebar"]);
511
511
  var SOURCED_VALUE_ACTIONS = /* @__PURE__ */ new Set(["fill", "select"]);
512
- var ENV_PLACEHOLDER2 = /^\s*\{\{env\.[A-Za-z_][A-Za-z0-9_]*\}\}\s*$/;
512
+ function referencesOnlyNamedVars(value, step) {
513
+ const named = new Set(referencedEnvVars(step));
514
+ return referencedEnvVars(value).every((name) => named.has(name));
515
+ }
513
516
  function normalise(text) {
514
517
  return text.replace(/\s+/g, " ").trim().toLowerCase();
515
518
  }
@@ -545,7 +548,16 @@ var StepRecovery = class {
545
548
  * step — which is also the whole of the "does not cross steps" rule.
546
549
  */
547
550
  readable;
551
+ /**
552
+ * The step exactly as written, for deciding which `{{env.*}}` variables it
553
+ * names (design env-placeholder-must-be-named, D3). Kept alongside the
554
+ * normalised copy rather than derived from it: `readable` is lowercased, and
555
+ * environment variable names are case-sensitive, so `TOKEN` and `token` must
556
+ * stay distinguishable in the one place that decides which secret gets typed.
557
+ */
558
+ step;
548
559
  constructor(step) {
560
+ this.step = step;
549
561
  this.readable = [normalise(step)];
550
562
  }
551
563
  /**
@@ -614,7 +626,11 @@ var StepRecovery = class {
614
626
  if (!SOURCED_VALUE_ACTIONS.has(action.action)) return void 0;
615
627
  const value = action.value;
616
628
  if (!value) return void 0;
617
- if (ENV_PLACEHOLDER2.test(value)) return void 0;
629
+ if (!referencesOnlyNamedVars(value, this.step)) {
630
+ const named = referencedEnvVars(this.step);
631
+ return `refused: the value "${value}" was NOT typed, because this step does not reference that {{env.*}} variable. ${named.length > 0 ? `This step references ${named.map((n) => `{{env.${n}}}`).join(", ")}.` : "This step references no environment variable."} A placeholder is a source only when the step names it \u2014 otherwise the test never asked for that secret to be typed here. Use a value this step supplies, or fail the step and say it supplies none.`;
632
+ }
633
+ if (referencedEnvVars(value).length > 0) return void 0;
618
634
  const needle = normalise(value);
619
635
  if (needle === "") return void 0;
620
636
  if (this.readable.some((source) => source.includes(needle))) return void 0;
@@ -2990,7 +3006,7 @@ function parsePositiveNumber(flag) {
2990
3006
  };
2991
3007
  }
2992
3008
  var program = new Command();
2993
- program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.14.0");
3009
+ program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.15.0");
2994
3010
  program.command("init").description("Scaffold .blastproof/ (config, tests, sample tests) in the current directory").action(async () => {
2995
3011
  try {
2996
3012
  const result = await initProject(process.cwd());