execbro 2.9.8 → 2.10.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 +8 -1
- package/build/__tests__/unit/httpRequest.test.d.ts +2 -0
- package/build/__tests__/unit/httpRequest.test.d.ts.map +1 -0
- package/build/__tests__/unit/httpRequest.test.js +127 -0
- package/build/__tests__/unit/httpRequest.test.js.map +1 -0
- package/build/__tests__/unit/redact.test.d.ts +2 -0
- package/build/__tests__/unit/redact.test.d.ts.map +1 -0
- package/build/__tests__/unit/redact.test.js +98 -0
- package/build/__tests__/unit/redact.test.js.map +1 -0
- package/build/__tests__/unit/requestDetailsInclude.test.js +25 -3
- package/build/__tests__/unit/requestDetailsInclude.test.js.map +1 -1
- package/build/__tests__/unit/requestDetailsProjection.test.js +75 -1
- package/build/__tests__/unit/requestDetailsProjection.test.js.map +1 -1
- package/build/__tests__/unit/untrustedDataRule.test.d.ts +2 -0
- package/build/__tests__/unit/untrustedDataRule.test.d.ts.map +1 -0
- package/build/__tests__/unit/untrustedDataRule.test.js +32 -0
- package/build/__tests__/unit/untrustedDataRule.test.js.map +1 -0
- package/build/__tests__/unit/vault.test.d.ts +2 -0
- package/build/__tests__/unit/vault.test.d.ts.map +1 -0
- package/build/__tests__/unit/vault.test.js +116 -0
- package/build/__tests__/unit/vault.test.js.map +1 -0
- package/build/__tests__/unit/vaultCapture.test.d.ts +2 -0
- package/build/__tests__/unit/vaultCapture.test.d.ts.map +1 -0
- package/build/__tests__/unit/vaultCapture.test.js +47 -0
- package/build/__tests__/unit/vaultCapture.test.js.map +1 -0
- package/build/__tests__/unit/vaultSlots.test.d.ts +2 -0
- package/build/__tests__/unit/vaultSlots.test.d.ts.map +1 -0
- package/build/__tests__/unit/vaultSlots.test.js +64 -0
- package/build/__tests__/unit/vaultSlots.test.js.map +1 -0
- package/build/__tests__/unit/vaultTools.test.d.ts +2 -0
- package/build/__tests__/unit/vaultTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/vaultTools.test.js +49 -0
- package/build/__tests__/unit/vaultTools.test.js.map +1 -0
- package/build/core/guides.d.ts +1 -0
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +37 -6
- package/build/core/guides.js.map +1 -1
- package/build/core/httpRequest.d.ts +18 -0
- package/build/core/httpRequest.d.ts.map +1 -0
- package/build/core/httpRequest.js +95 -0
- package/build/core/httpRequest.js.map +1 -0
- package/build/core/network.d.ts +1 -1
- package/build/core/network.d.ts.map +1 -1
- package/build/core/network.js +19 -10
- package/build/core/network.js.map +1 -1
- package/build/core/redact.d.ts +6 -0
- package/build/core/redact.d.ts.map +1 -0
- package/build/core/redact.js +78 -0
- package/build/core/redact.js.map +1 -0
- package/build/core/register.d.ts.map +1 -1
- package/build/core/register.js +14 -0
- package/build/core/register.js.map +1 -1
- package/build/core/vault.d.ts +21 -0
- package/build/core/vault.d.ts.map +1 -0
- package/build/core/vault.js +168 -0
- package/build/core/vault.js.map +1 -0
- package/build/core/vaultCapture.d.ts +2 -0
- package/build/core/vaultCapture.d.ts.map +1 -0
- package/build/core/vaultCapture.js +38 -0
- package/build/core/vaultCapture.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +11 -8
- package/build/index.js.map +1 -1
- package/build/tools/metaTools.d.ts.map +1 -1
- package/build/tools/metaTools.js +36 -0
- package/build/tools/metaTools.js.map +1 -1
- package/build/tools/networkTools.js +1 -1
- package/build/tools/networkTools.js.map +1 -1
- package/build/tools/requestTools.d.ts.map +1 -1
- package/build/tools/requestTools.js +49 -1
- package/build/tools/requestTools.js.map +1 -1
- package/package.json +1 -1
- package/tools.json +19 -2
package/README.md
CHANGED
|
@@ -50,6 +50,13 @@ Linking is also how [ExecBro Pro](#pricing) attaches to your installation, if yo
|
|
|
50
50
|
- **Accessibility Inspection** - Query UI hierarchy to find elements by text, label, or resource ID
|
|
51
51
|
- **OCR Text Extraction** - Extract visible text with tap-ready coordinates via Google Cloud Vision (works on any screen content)
|
|
52
52
|
|
|
53
|
+
### Credential Safety
|
|
54
|
+
|
|
55
|
+
- **Secrets never enter the transcript** - Every tool's output passes one redaction chokepoint, so a token is replaced by a handle (`[secret:auth_api.acme.io]`) whether it appears in a network header, a Redux store, a log line or a URL. Credential headers are matched by pattern rather than a fixed list, so vendor-namespaced ones (`x-shopify-access-token`, `x-goog-api-key`, `x-hasura-admin-secret`) are covered too, while `x-request-id` and `x-idempotency-key` are deliberately left alone. There is no per-call escape: `EXECBRO_REDACT=off` is set by a human and needs a restart
|
|
56
|
+
- **Use a credential without reading it** - `list_secrets` names the handles; `http_request({auth:{secret:"api.acme.io"}})` substitutes the value host-side and issues the request from your machine, and `vault_capture` reads a token out of the app straight into the vault when no captured request revealed one. Each credential is bound to the origin it was observed on and refused elsewhere; the vault is memory-only
|
|
57
|
+
- **Server- vs client-side, isolated** - `http_request` runs from the host with none of the app's TLS trust, proxy, cookie jar or mock rules; `app_request` runs inside the app with all of them. Comparing the two is how you tell a backend bug from a client one — and a 401 from the host where the app succeeds is itself the answer that the backend enforces attestation
|
|
58
|
+
- **App data is treated as data** - The server tells every connecting agent that logs, payloads, component trees and eval results are shaped by whatever the app talked to, and are never to be followed as instructions
|
|
59
|
+
|
|
53
60
|
### Multi-Device Debugging
|
|
54
61
|
|
|
55
62
|
- **Connect All Devices** - `scan_metro` automatically discovers and connects to all Bridgeless targets on each Metro port
|
|
@@ -170,7 +177,7 @@ See the [full tool reference](docs/tools.md) for all tools with descriptions. Ke
|
|
|
170
177
|
| [Device Interaction](docs/device-interaction.md) | Unified `tap`, platform-specific gestures, text input, key events |
|
|
171
178
|
| [OCR Text Extraction](docs/ocr.md) | Cloud Vision OCR, offline fallback, language config, workflows |
|
|
172
179
|
| [Claude Code Skills](docs/skills.md) | Pre-built skills for session setup, debugging, and automation |
|
|
173
|
-
| [Full Tool Reference](docs/tools.md) | Complete list of all
|
|
180
|
+
| [Full Tool Reference](docs/tools.md) | Complete list of all 63 tools with descriptions |
|
|
174
181
|
|
|
175
182
|
## How It Works
|
|
176
183
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpRequest.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/httpRequest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from "@jest/globals";
|
|
2
|
+
import { resolveAuth, issueHttpRequest } from "../../core/httpRequest.js";
|
|
3
|
+
import { vaultAdd, resetVaultForTests } from "../../core/vault.js";
|
|
4
|
+
import { UserInputError } from "../../core/errors.js";
|
|
5
|
+
const TOKEN = "opaque-session-id-abcdefgh";
|
|
6
|
+
const DEAD_JWT = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjEwMDAwMDAwMDB9.Zm9vYmFyc2lnbmF0dXJlZm9vYmFy";
|
|
7
|
+
function mockFetch(body, init = {}) {
|
|
8
|
+
const res = new Response(body, {
|
|
9
|
+
status: init.status ?? 200,
|
|
10
|
+
headers: init.headers ?? { "content-type": "application/json" },
|
|
11
|
+
});
|
|
12
|
+
const spy = jest.fn(async () => res);
|
|
13
|
+
globalThis.fetch = spy;
|
|
14
|
+
return spy;
|
|
15
|
+
}
|
|
16
|
+
const realFetch = globalThis.fetch;
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
globalThis.fetch = realFetch;
|
|
19
|
+
});
|
|
20
|
+
describe("resolveAuth origin binding", () => {
|
|
21
|
+
beforeEach(resetVaultForTests);
|
|
22
|
+
it("attaches a credential to its own origin", () => {
|
|
23
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
24
|
+
const { header } = resolveAuth({ secret: "api.acme.io" }, "https://api.acme.io/v1/orders");
|
|
25
|
+
expect(header).toBe(`Bearer ${TOKEN}`);
|
|
26
|
+
});
|
|
27
|
+
it("refuses to send a credential to a different origin, which is the exfiltration case", () => {
|
|
28
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
29
|
+
expect(() => resolveAuth({ secret: "api.acme.io" }, "https://evil.com/collect")).toThrow(UserInputError);
|
|
30
|
+
try {
|
|
31
|
+
resolveAuth({ secret: "api.acme.io" }, "https://evil.com/collect");
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
expect(e.message).toContain("api.acme.io");
|
|
35
|
+
expect(e.message).toContain("evil.com");
|
|
36
|
+
expect(e.message).not.toContain(TOKEN);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
it("refuses a handle pointing at another origin, so a handle is no way around binding", () => {
|
|
40
|
+
const handle = vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
41
|
+
expect(() => resolveAuth({ secret: handle }, "https://evil.com/collect")).toThrow(UserInputError);
|
|
42
|
+
});
|
|
43
|
+
it("names the unknown secret without inventing one", () => {
|
|
44
|
+
expect(() => resolveAuth({ secret: "api.acme.io" }, "https://api.acme.io/x")).toThrow(/no credential/i);
|
|
45
|
+
});
|
|
46
|
+
it("returns a staleness note for an expired credential rather than failing silently", () => {
|
|
47
|
+
vaultAdd(DEAD_JWT, "auth", "https://api.acme.io/v1/me");
|
|
48
|
+
const { header, note } = resolveAuth({ secret: "api.acme.io" }, "https://api.acme.io/v1/orders");
|
|
49
|
+
expect(header).toBe(`Bearer ${DEAD_JWT}`);
|
|
50
|
+
expect(note).toContain("expired");
|
|
51
|
+
});
|
|
52
|
+
it("attaches nothing when no auth was asked for", () => {
|
|
53
|
+
expect(resolveAuth(undefined, "https://api.acme.io/x")).toEqual({});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("issueHttpRequest", () => {
|
|
57
|
+
beforeEach(resetVaultForTests);
|
|
58
|
+
it("rejects a non-HTTP scheme", async () => {
|
|
59
|
+
await expect(issueHttpRequest({ method: "GET", url: "file:///etc/passwd" })).rejects.toThrow(UserInputError);
|
|
60
|
+
});
|
|
61
|
+
it("rejects a malformed URL", async () => {
|
|
62
|
+
await expect(issueHttpRequest({ method: "GET", url: "not a url" })).rejects.toThrow(UserInputError);
|
|
63
|
+
});
|
|
64
|
+
it("sends the resolved credential and renders the response", async () => {
|
|
65
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
66
|
+
const spy = mockFetch(JSON.stringify({ ok: true }));
|
|
67
|
+
const out = await issueHttpRequest({
|
|
68
|
+
method: "GET",
|
|
69
|
+
url: "https://api.acme.io/v1/orders",
|
|
70
|
+
auth: { secret: "api.acme.io" },
|
|
71
|
+
});
|
|
72
|
+
const init = spy.mock.calls[0][1];
|
|
73
|
+
expect(init.headers.Authorization).toBe(`Bearer ${TOKEN}`);
|
|
74
|
+
expect(out).toContain("200");
|
|
75
|
+
expect(out).toContain('"ok":true');
|
|
76
|
+
});
|
|
77
|
+
it("never renders the credential it just sent", async () => {
|
|
78
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
79
|
+
mockFetch(JSON.stringify({ received: { authorization: `Bearer ${TOKEN}` } }));
|
|
80
|
+
const out = await issueHttpRequest({
|
|
81
|
+
method: "GET",
|
|
82
|
+
url: "https://api.acme.io/v1/echo",
|
|
83
|
+
auth: { secret: "api.acme.io" },
|
|
84
|
+
});
|
|
85
|
+
expect(out).not.toContain(TOKEN);
|
|
86
|
+
expect(out).toContain("[secret:auth_api.acme.io]");
|
|
87
|
+
});
|
|
88
|
+
it("serialises an object body as JSON and sets the content type", async () => {
|
|
89
|
+
const spy = mockFetch("{}");
|
|
90
|
+
await issueHttpRequest({ method: "POST", url: "https://api.acme.io/v1/x", body: { qty: 2 } });
|
|
91
|
+
const init = spy.mock.calls[0][1];
|
|
92
|
+
expect(init.body).toBe('{"qty":2}');
|
|
93
|
+
expect(init.headers["Content-Type"]).toBe("application/json");
|
|
94
|
+
});
|
|
95
|
+
it("sends a string body verbatim without imposing a content type", async () => {
|
|
96
|
+
const spy = mockFetch("{}");
|
|
97
|
+
await issueHttpRequest({
|
|
98
|
+
method: "POST",
|
|
99
|
+
url: "https://api.acme.io/v1/x",
|
|
100
|
+
body: "a=1&b=2",
|
|
101
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
102
|
+
});
|
|
103
|
+
const init = spy.mock.calls[0][1];
|
|
104
|
+
expect(init.body).toBe("a=1&b=2");
|
|
105
|
+
expect(init.headers["Content-Type"]).toBe("application/x-www-form-urlencoded");
|
|
106
|
+
});
|
|
107
|
+
it("surfaces the staleness note through the WARNING channel", async () => {
|
|
108
|
+
vaultAdd(DEAD_JWT, "auth", "https://api.acme.io/v1/me");
|
|
109
|
+
mockFetch(JSON.stringify({ error: "unauthorized" }), { status: 401 });
|
|
110
|
+
const out = await issueHttpRequest({
|
|
111
|
+
method: "GET",
|
|
112
|
+
url: "https://api.acme.io/v1/orders",
|
|
113
|
+
auth: { secret: "api.acme.io" },
|
|
114
|
+
});
|
|
115
|
+
expect(out).toContain("WARNING:");
|
|
116
|
+
expect(out).toContain("expired");
|
|
117
|
+
});
|
|
118
|
+
it("reports a transport failure as a message, not an unhandled rejection", async () => {
|
|
119
|
+
globalThis.fetch = jest.fn(async () => {
|
|
120
|
+
throw new Error("getaddrinfo ENOTFOUND api.acme.io");
|
|
121
|
+
});
|
|
122
|
+
const out = await issueHttpRequest({ method: "GET", url: "https://api.acme.io/v1/x" });
|
|
123
|
+
expect(out).toContain("Request failed");
|
|
124
|
+
expect(out).toContain("ENOTFOUND");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=httpRequest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpRequest.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/httpRequest.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/redact.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { redactSecrets } from "../../core/redact.js";
|
|
2
|
+
import { resetVaultForTests, vaultAdd } from "../../core/vault.js";
|
|
3
|
+
const JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
4
|
+
describe("redactSecrets", () => {
|
|
5
|
+
beforeEach(resetVaultForTests);
|
|
6
|
+
it("renders a JWT as a stable handle and never as its value", () => {
|
|
7
|
+
const out = redactSecrets(`token was ${JWT} ok`);
|
|
8
|
+
expect(out).not.toContain(JWT);
|
|
9
|
+
expect(out).toContain("[secret:jwt_app]");
|
|
10
|
+
});
|
|
11
|
+
it("gives the same value the same handle across two calls, which is cross-sink identity", () => {
|
|
12
|
+
const fromState = redactSecrets(`{"accessToken":"${JWT}"}`);
|
|
13
|
+
const fromHeader = redactSecrets(`Authorization: Bearer ${JWT}`);
|
|
14
|
+
expect(fromState).toContain("[secret:jwt_app]");
|
|
15
|
+
expect(fromHeader).toContain("[secret:jwt_app]");
|
|
16
|
+
});
|
|
17
|
+
it("masks a value seen once by exact match afterwards, with no shape or key to match", () => {
|
|
18
|
+
redactSecrets("Authorization: Bearer opaque-session-id-abcdefgh");
|
|
19
|
+
const later = redactSecrets('ws frame: {"t":"opaque-session-id-abcdefgh"}', { catalog: false });
|
|
20
|
+
expect(later).not.toContain("opaque-session-id-abcdefgh");
|
|
21
|
+
expect(later).toContain("[secret:auth_app]");
|
|
22
|
+
});
|
|
23
|
+
it("names a provider key by its verified shape", () => {
|
|
24
|
+
expect(redactSecrets("key sk_live_51H8xKzAbCdEfGhIjKlMnOp here")).toContain("[secret:stripe_app]");
|
|
25
|
+
expect(redactSecrets("AKIAIOSFODNN7EXAMPLE")).toContain("[secret:aws_app]");
|
|
26
|
+
});
|
|
27
|
+
it("does not re-redact a marker a previous rule just produced", () => {
|
|
28
|
+
const out = redactSecrets(`{"accessToken":"${JWT}"}`);
|
|
29
|
+
expect(out).toContain('"accessToken":"[secret:jwt_app]"');
|
|
30
|
+
expect(out).not.toContain("[redacted secret");
|
|
31
|
+
});
|
|
32
|
+
it("keeps the auth scheme but not the credential", () => {
|
|
33
|
+
const out = redactSecrets("Authorization: Bearer abcdefghijklmnopqrstuvwxyz");
|
|
34
|
+
expect(out).toContain("Bearer [secret:auth_app]");
|
|
35
|
+
expect(out).not.toContain("abcdefghijklmnopqrstuvwxyz");
|
|
36
|
+
});
|
|
37
|
+
it("does not vault key-name matches, which are below the entry bar", () => {
|
|
38
|
+
const out = redactSecrets("GET https://api.example.com/v1/me?access_token=s3cr3tvalue&page=2");
|
|
39
|
+
expect(out).toContain("?access_token=[redacted secret,");
|
|
40
|
+
expect(out).toContain("&page=2");
|
|
41
|
+
expect(out).not.toContain("s3cr3tvalue");
|
|
42
|
+
expect(out).not.toContain("[secret:");
|
|
43
|
+
});
|
|
44
|
+
it("redacts secret-named JSON fields, which covers redux state", () => {
|
|
45
|
+
const out = redactSecrets('{"auth":{"access_token":"opaque-session-1234","user":"bob"}}');
|
|
46
|
+
expect(out).toContain('"access_token":"[redacted secret,');
|
|
47
|
+
expect(out).not.toContain("opaque-session-1234");
|
|
48
|
+
expect(out).toContain('"user":"bob"');
|
|
49
|
+
});
|
|
50
|
+
it("no longer discloses the last four characters", () => {
|
|
51
|
+
const out = redactSecrets('{"access_token":"opaque-session-1234"}');
|
|
52
|
+
expect(out).not.toContain("1234]");
|
|
53
|
+
expect(out).toContain("[redacted secret, 19 chars]");
|
|
54
|
+
});
|
|
55
|
+
it("leaves placeholders alone, because an absent token is the answer", () => {
|
|
56
|
+
expect(redactSecrets("refresh_token: undefined")).toBe("refresh_token: undefined");
|
|
57
|
+
});
|
|
58
|
+
it("appends a catalog when a handle was emitted, and nothing otherwise", () => {
|
|
59
|
+
expect(redactSecrets(`Bearer ${JWT}`)).toContain("--- secrets referenced above ---");
|
|
60
|
+
expect(redactSecrets("plain text")).toBe("plain text");
|
|
61
|
+
});
|
|
62
|
+
it("omits the catalog when asked, for telemetry fields", () => {
|
|
63
|
+
const out = redactSecrets(`Bearer ${JWT}`, { catalog: false });
|
|
64
|
+
expect(out).not.toContain("--- secrets referenced above ---");
|
|
65
|
+
expect(out).toContain("[secret:jwt_app]");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe("catalog coverage for handles emitted elsewhere", () => {
|
|
69
|
+
beforeEach(resetVaultForTests);
|
|
70
|
+
it("explains a handle that redactHeaderValue rendered, not this call", () => {
|
|
71
|
+
const handle = vaultAdd("opaque-session-id-abcdefgh", "auth", "https://gifted.fyi/app-api/me");
|
|
72
|
+
const out = redactSecrets(`Authorization: Bearer [secret:${handle}]`);
|
|
73
|
+
expect(out).toContain("--- secrets referenced above ---");
|
|
74
|
+
expect(out).toContain("auth_gifted.fyi: auth seen on gifted.fyi");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe("a value that already carries a handle", () => {
|
|
78
|
+
beforeEach(resetVaultForTests);
|
|
79
|
+
const TOKEN = "opaque-session-id-abcdefgh";
|
|
80
|
+
it("keeps the handle when a scheme prefix precedes it", () => {
|
|
81
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
82
|
+
const out = redactSecrets(JSON.stringify({ authorization: `Bearer ${TOKEN}` }), { catalog: false });
|
|
83
|
+
expect(out).toContain("Bearer [secret:auth_api.acme.io]");
|
|
84
|
+
expect(out).not.toContain("[redacted secret");
|
|
85
|
+
});
|
|
86
|
+
it("keeps the handle among cookie attributes", () => {
|
|
87
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
88
|
+
const out = redactSecrets(JSON.stringify({ cookie: `sid=${TOKEN}; path=/` }), { catalog: false });
|
|
89
|
+
expect(out).toContain("sid=[secret:auth_api.acme.io]; path=/");
|
|
90
|
+
});
|
|
91
|
+
it("still redacts a second, unmatched secret sitting beside a handle", () => {
|
|
92
|
+
vaultAdd(TOKEN, "auth", "https://api.acme.io/v1/me");
|
|
93
|
+
const out = redactSecrets(JSON.stringify({ authorization: `Bearer ${TOKEN} sig=OTHERSECRETVALUE1234567890` }), { catalog: false });
|
|
94
|
+
expect(out).not.toContain("OTHERSECRETVALUE1234567890");
|
|
95
|
+
expect(out).toContain("[redacted secret");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=redact.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/redact.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect } from "@jest/globals";
|
|
1
|
+
import { describe, it, expect, beforeEach } from "@jest/globals";
|
|
2
2
|
import { formatRequestDetails, redactHeaderValue } from "../../core/network.js";
|
|
3
|
+
import { resetVaultForTests, vaultByHandle } from "../../core/vault.js";
|
|
3
4
|
const JWT = "Bearer " + "e".repeat(1500);
|
|
4
5
|
function request(overrides = {}) {
|
|
5
6
|
return {
|
|
@@ -18,10 +19,11 @@ function request(overrides = {}) {
|
|
|
18
19
|
}
|
|
19
20
|
describe("get_request_details request/response split", () => {
|
|
20
21
|
it("keeps a live token out of the transcript unless verbose asks for it", () => {
|
|
22
|
+
resetVaultForTests();
|
|
21
23
|
const out = formatRequestDetails(request(), { include: "request" });
|
|
22
24
|
expect(out).not.toContain(JWT);
|
|
23
|
-
expect(out).toContain("Bearer [
|
|
24
|
-
expect(formatRequestDetails(request(), { include: "request", verbose: true })).toContain(JWT);
|
|
25
|
+
expect(out).toContain("Bearer [secret:auth_api.example.com]");
|
|
26
|
+
expect(formatRequestDetails(request(), { include: "request", verbose: true })).not.toContain(JWT);
|
|
25
27
|
expect(redactHeaderValue("accept", "application/json")).toBe("application/json");
|
|
26
28
|
});
|
|
27
29
|
it("renders only the queried side, so narrowing does not re-dump the request", () => {
|
|
@@ -43,4 +45,24 @@ describe("get_request_details request/response split", () => {
|
|
|
43
45
|
expect(out).toContain("t1");
|
|
44
46
|
});
|
|
45
47
|
});
|
|
48
|
+
describe("redactHeaderValue and the vault", () => {
|
|
49
|
+
beforeEach(resetVaultForTests);
|
|
50
|
+
it("names the handle after the request host", () => {
|
|
51
|
+
const out = redactHeaderValue("authorization", "Bearer opaque-session-id-abcdefgh", "https://api.acme.io/v1/me");
|
|
52
|
+
expect(out).toBe("Bearer [secret:auth_api.acme.io]");
|
|
53
|
+
expect(vaultByHandle("auth_api.acme.io").origin).toBe("api.acme.io");
|
|
54
|
+
});
|
|
55
|
+
it("vaults the token without its scheme, so the same value has one identity everywhere", () => {
|
|
56
|
+
redactHeaderValue("authorization", "Bearer opaque-session-id-abcdefgh", "https://api.acme.io/v1/me");
|
|
57
|
+
expect(vaultByHandle("auth_api.acme.io").value).toBe("opaque-session-id-abcdefgh");
|
|
58
|
+
});
|
|
59
|
+
it("falls back to a shape description for a short credential header", () => {
|
|
60
|
+
const out = redactHeaderValue("x-api-key", "abc123", "https://api.acme.io/v1/me");
|
|
61
|
+
expect(out).toContain("[redacted, 6 chars");
|
|
62
|
+
expect(out).not.toContain("abc123");
|
|
63
|
+
});
|
|
64
|
+
it("still leaves a non-credential header alone", () => {
|
|
65
|
+
expect(redactHeaderValue("accept", "application/json")).toBe("application/json");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
46
68
|
//# sourceMappingURL=requestDetailsInclude.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requestDetailsInclude.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/requestDetailsInclude.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"requestDetailsInclude.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/requestDetailsInclude.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect } from "@jest/globals";
|
|
1
|
+
import { describe, it, expect, beforeEach } from "@jest/globals";
|
|
2
2
|
import { formatRequestDetails } from "../../core/network.js";
|
|
3
|
+
import { resetVaultForTests } from "../../core/vault.js";
|
|
3
4
|
function request(overrides = {}) {
|
|
4
5
|
return {
|
|
5
6
|
requestId: "js-1",
|
|
@@ -66,4 +67,77 @@ describe("get_request_details body rendering", () => {
|
|
|
66
67
|
expect(text).toContain(graphqlResponse);
|
|
67
68
|
});
|
|
68
69
|
});
|
|
70
|
+
describe("credential headers", () => {
|
|
71
|
+
const JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
72
|
+
const VALUE = "abc123def456ghi789jkl";
|
|
73
|
+
const render = (headers, verbose = false) => formatRequestDetails(request({ headers }), { verbose });
|
|
74
|
+
it.each([
|
|
75
|
+
"authorization",
|
|
76
|
+
"cookie",
|
|
77
|
+
"x-api-key",
|
|
78
|
+
"apikey",
|
|
79
|
+
"api-key",
|
|
80
|
+
"x-goog-api-key",
|
|
81
|
+
"x-algolia-api-key",
|
|
82
|
+
"x-shopify-access-token",
|
|
83
|
+
"x-amz-security-token",
|
|
84
|
+
"x-hasura-admin-secret",
|
|
85
|
+
"x-firebase-appcheck",
|
|
86
|
+
"x-access-token",
|
|
87
|
+
"x-refresh-token",
|
|
88
|
+
"x-session-token",
|
|
89
|
+
"x-client-token",
|
|
90
|
+
"x-csrf-token",
|
|
91
|
+
"x-xsrf-token",
|
|
92
|
+
"sec-websocket-protocol",
|
|
93
|
+
"token"
|
|
94
|
+
])("redacts %s", (name) => {
|
|
95
|
+
expect(render({ [name]: VALUE })).not.toContain(VALUE);
|
|
96
|
+
});
|
|
97
|
+
it.each([
|
|
98
|
+
"x-request-id",
|
|
99
|
+
"x-correlation-id",
|
|
100
|
+
"x-amzn-trace-id",
|
|
101
|
+
"x-idempotency-key",
|
|
102
|
+
"x-continuation-token",
|
|
103
|
+
"content-type"
|
|
104
|
+
])("leaves %s alone", (name) => {
|
|
105
|
+
expect(render({ [name]: VALUE })).toContain(VALUE);
|
|
106
|
+
});
|
|
107
|
+
beforeEach(resetVaultForTests);
|
|
108
|
+
it.each([false, true])("stays redacted with verbose:%s", (verbose) => {
|
|
109
|
+
const out = render({ authorization: `Bearer ${JWT}` }, verbose);
|
|
110
|
+
expect(out).not.toContain(JWT);
|
|
111
|
+
expect(out).toContain("Bearer [secret:auth_api.example.com]");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe("redaction runs before structural bounding", () => {
|
|
115
|
+
beforeEach(resetVaultForTests);
|
|
116
|
+
const BIG_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
|
|
117
|
+
"e" + "y".repeat(1200) +
|
|
118
|
+
".SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
119
|
+
function tokenResponse() {
|
|
120
|
+
return request({
|
|
121
|
+
url: "https://login.microsoftonline.com/tenant/oauth2/v2.0/token",
|
|
122
|
+
responseBody: JSON.stringify({ token_type: "Bearer", access_token: BIG_JWT, expires_in: 4887 })
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
it("gives a long token in a body a handle instead of a truncated shape description", () => {
|
|
126
|
+
const out = formatRequestDetails(tokenResponse(), { include: "response", maxBodyLength: 400 });
|
|
127
|
+
expect(out).not.toContain(BIG_JWT);
|
|
128
|
+
expect(out).toContain("[secret:jwt_login.microsoftonline.com]");
|
|
129
|
+
expect(out).not.toContain("[redacted secret,");
|
|
130
|
+
});
|
|
131
|
+
it("links a body token to the same token seen in a header, which is the whole point", () => {
|
|
132
|
+
formatRequestDetails(tokenResponse(), { include: "response", maxBodyLength: 400 });
|
|
133
|
+
const sent = formatRequestDetails(request({ url: "https://api.example.com/graphql", headers: { authorization: `Bearer ${BIG_JWT}` } }), { include: "request" });
|
|
134
|
+
expect(sent).toContain("[secret:jwt_login.microsoftonline.com]");
|
|
135
|
+
});
|
|
136
|
+
it("keeps the body valid JSON after substitution", () => {
|
|
137
|
+
const out = formatRequestDetails(tokenResponse(), { include: "response", maxBodyLength: 0 });
|
|
138
|
+
const body = out.split("--- Response Body ---")[1].split("--- secrets")[0].trim();
|
|
139
|
+
expect(() => JSON.parse(body)).not.toThrow();
|
|
140
|
+
expect(JSON.parse(body).access_token).toBe("[secret:jwt_login.microsoftonline.com]");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
69
143
|
//# sourceMappingURL=requestDetailsProjection.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requestDetailsProjection.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/requestDetailsProjection.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"requestDetailsProjection.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/requestDetailsProjection.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"untrustedDataRule.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/untrustedDataRule.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
process.env.RN_AI_DEVTOOLS_TEST_MODE = "1";
|
|
2
|
+
import { describe, it, expect } from "@jest/globals";
|
|
3
|
+
import { UNTRUSTED_DATA_RULE } from "../../core/guides.js";
|
|
4
|
+
import { SERVER_INSTRUCTIONS } from "../../index.js";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
describe("untrusted-data rule", () => {
|
|
8
|
+
it("names every surface that carries attacker-influencable content", () => {
|
|
9
|
+
for (const surface of ["og", "etwork", "omponent", "source", "execute_in_app"]) {
|
|
10
|
+
expect(UNTRUSTED_DATA_RULE).toContain(surface);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
it("states the data-not-instructions rule", () => {
|
|
14
|
+
expect(UNTRUSTED_DATA_RULE.toLowerCase()).toContain("data, not instructions");
|
|
15
|
+
expect(UNTRUSTED_DATA_RULE.toLowerCase()).toContain("never follow");
|
|
16
|
+
});
|
|
17
|
+
it("tells the agent what to do instead of copying a credential", () => {
|
|
18
|
+
expect(UNTRUSTED_DATA_RULE).toContain("[secret:");
|
|
19
|
+
expect(UNTRUSTED_DATA_RULE).toContain("http_request");
|
|
20
|
+
});
|
|
21
|
+
it("is actually wired into the instructions the server sends", () => {
|
|
22
|
+
expect(SERVER_INSTRUCTIONS).toContain(UNTRUSTED_DATA_RULE);
|
|
23
|
+
});
|
|
24
|
+
it("reaches every skill that surfaces untrusted content", () => {
|
|
25
|
+
const risky = ["network-inspect", "debug-logs", "component-inspect", "app-state", "layout-check"];
|
|
26
|
+
for (const skill of risky) {
|
|
27
|
+
const text = readFileSync(join(process.cwd(), "skills", `${skill}.md`), "utf8");
|
|
28
|
+
expect(text.toLowerCase()).toContain("data, not instructions");
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=untrustedDataRule.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"untrustedDataRule.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/untrustedDataRule.test.ts"],"names":[],"mappings":"AAOA,OAAO,CAAC,GAAG,CAAC,wBAAwB,GAAG,GAAG,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QAGtE,KAAK,MAAM,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,gBAAgB,CAAC,EAAE,CAAC;YAC7E,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAC9E,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QAGlE,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAEhE,MAAM,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAI3D,MAAM,KAAK,GAAG,CAAC,iBAAiB,EAAE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;QAClG,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,GAAG,KAAK,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;YAChF,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QACnE,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/vault.test.ts"],"names":[],"mappings":""}
|