blastproof 0.7.0 → 0.8.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 +4 -2
- package/dist/cli.js +31 -6
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ jobs:
|
|
|
113
113
|
|
|
114
114
|
- run: npm start & # however your app boots
|
|
115
115
|
|
|
116
|
-
- uses: hamc/blastproof@v0.
|
|
116
|
+
- uses: hamc/blastproof@v0.8.0
|
|
117
117
|
with:
|
|
118
118
|
version: '0.6.0' # pin both when this gates merges
|
|
119
119
|
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
@@ -241,7 +241,9 @@ The key itself is never read from a `BLASTPROOF_*` variable — you name *which*
|
|
|
241
241
|
|
|
242
242
|
The application under test is not trusted input: its page content reaches the model, so a page that controls its own accessible text can try to influence the agent. Two things constrain that.
|
|
243
243
|
|
|
244
|
-
**The agent cannot leave your application.**
|
|
244
|
+
**The agent cannot leave your application.** The boundary is `base_url`'s origin plus whatever `allowed_origins:` declares, and it constrains where the page **is**, not only where an action asked to go. A `navigate` outside it is refused before the request; a page that ends up outside it any other way — a redirect, a link to another host, a script setting the location — fails the step, and its content is never sent to the model. Enforced by comparison, not by asking the model nicely.
|
|
245
|
+
|
|
246
|
+
If your application legitimately spans hosts (an identity provider, a hosted payment step), declare them. A suite that was quietly walking onto a foreign page will now fail and name the origin to add.
|
|
245
247
|
|
|
246
248
|
**Your secrets stay out of prompts.** `{{env.*}}` placeholders survive intact and are substituted at the moment of typing. Every value your tests or auth recipe reference is redacted from everything else crossing into a prompt — page snapshots included — in literal and percent-encoded form. Redaction matches known values, so treat it as a strong default rather than a guarantee against a hostile app.
|
|
247
249
|
|
package/dist/cli.js
CHANGED
|
@@ -336,14 +336,33 @@ var ActionError = class extends Error {
|
|
|
336
336
|
this.name = "ActionError";
|
|
337
337
|
}
|
|
338
338
|
};
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
339
|
+
var BLANK_PAGE = "about:blank";
|
|
340
|
+
function allowedOriginsFor(baseUrl, allowedOrigins) {
|
|
341
|
+
const allowed = /* @__PURE__ */ new Set([new URL(baseUrl).origin]);
|
|
342
|
+
for (const origin of allowedOrigins ?? []) {
|
|
342
343
|
allowed.add(new URL(origin).origin);
|
|
343
344
|
}
|
|
344
|
-
|
|
345
|
+
return allowed;
|
|
346
|
+
}
|
|
347
|
+
function isOriginAllowed(url, allowed) {
|
|
348
|
+
if (url === BLANK_PAGE) return true;
|
|
349
|
+
let origin;
|
|
350
|
+
try {
|
|
351
|
+
origin = new URL(url).origin;
|
|
352
|
+
} catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
if (origin === "null") return false;
|
|
356
|
+
return allowed.has(origin);
|
|
357
|
+
}
|
|
358
|
+
function describeBoundary(allowed) {
|
|
359
|
+
return [...allowed].join(" or ");
|
|
360
|
+
}
|
|
361
|
+
function assertAllowedOrigin(url, ctx) {
|
|
362
|
+
const allowed = allowedOriginsFor(ctx.baseUrl, ctx.allowedOrigins);
|
|
363
|
+
if (!isOriginAllowed(url.toString(), allowed)) {
|
|
345
364
|
throw new ActionError(
|
|
346
|
-
`Refusing to navigate outside the application: ${url.origin} is not ${
|
|
365
|
+
`Refusing to navigate outside the application: ${url.origin} is not ${describeBoundary(allowed)}. Add it to allowed_origins in .blastproof/config.yaml if the app legitimately spans hosts.`
|
|
347
366
|
);
|
|
348
367
|
}
|
|
349
368
|
}
|
|
@@ -548,6 +567,7 @@ async function executeTest(page, test, options) {
|
|
|
548
567
|
onEvent({ type: "action", index, action: maskedAction, result: mask(result) });
|
|
549
568
|
};
|
|
550
569
|
let failure;
|
|
570
|
+
const boundary = allowedOriginsFor(baseUrl, allowedOrigins);
|
|
551
571
|
await page.goto(new URL(baseUrl).toString());
|
|
552
572
|
for (let index = 0; index < allSteps.length; index++) {
|
|
553
573
|
const { step, setup } = allSteps[index];
|
|
@@ -564,6 +584,11 @@ async function executeTest(page, test, options) {
|
|
|
564
584
|
throw new StepFailure(`step exceeded ${maxIterationsPerStep} actions without completing`);
|
|
565
585
|
}
|
|
566
586
|
await waitForSettled(page);
|
|
587
|
+
if (!isOriginAllowed(page.url(), boundary)) {
|
|
588
|
+
throw new StepFailure(
|
|
589
|
+
`The page left the application: ${page.url()} is outside ${describeBoundary(boundary)}. Add it to allowed_origins in .blastproof/config.yaml if the app legitimately spans hosts.`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
567
592
|
const snap = await takeSnapshot(page);
|
|
568
593
|
let action;
|
|
569
594
|
try {
|
|
@@ -2635,7 +2660,7 @@ function parsePositiveNumber(flag) {
|
|
|
2635
2660
|
};
|
|
2636
2661
|
}
|
|
2637
2662
|
var program = new Command();
|
|
2638
|
-
program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.
|
|
2663
|
+
program.name("blastproof").description("Open-source AI testing agent: plain-English YAML tests executed agentically on a real browser.").version("0.8.0");
|
|
2639
2664
|
program.command("init").description("Scaffold .blastproof/ (config, tests, sample tests) in the current directory").action(async () => {
|
|
2640
2665
|
try {
|
|
2641
2666
|
const result = await initProject(process.cwd());
|