@secure-ai/guard 0.2.1 → 0.2.2
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/index.d.ts +20 -1
- package/dist/index.js +51 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -250,7 +250,7 @@ export declare class SecureAI {
|
|
|
250
250
|
* What to hand the wrapped function.
|
|
251
251
|
*
|
|
252
252
|
* The API omits `input` only on a block, so on any other decision it is
|
|
253
|
-
* there. The question is what to do
|
|
253
|
+
* there. The question is what to do when it is not, and the answer is not
|
|
254
254
|
* "send what the caller had".
|
|
255
255
|
*
|
|
256
256
|
* On a redact, the caller's input is the one thing that must not go: it
|
|
@@ -261,6 +261,25 @@ export declare class SecureAI {
|
|
|
261
261
|
*
|
|
262
262
|
* On an allow, nothing was rewritten and the caller's own input is the
|
|
263
263
|
* correct thing to pass, so the fallback is right there and stays.
|
|
264
|
+
*
|
|
265
|
+
* ── Why this asks which decisions may send, rather than which may not ──
|
|
266
|
+
*
|
|
267
|
+
* Two decisions mean "this may go": allow and redact. Every other value,
|
|
268
|
+
* present or future, means it may not. Asked the other way round — refuse
|
|
269
|
+
* on block, refuse on approve, send otherwise — a decision added to the
|
|
270
|
+
* policy later is sent by a client that has never heard of it, and the
|
|
271
|
+
* agent is told the check passed.
|
|
272
|
+
*
|
|
273
|
+
* That is not hypothetical here. It is the bug this library already had:
|
|
274
|
+
* "approve" was added, `guard` compared against "block" and nothing else,
|
|
275
|
+
* and an action a policy said must wait for a person went immediately.
|
|
276
|
+
* The Python comment on that branch still records it. The same shape cost
|
|
277
|
+
* ten fixes across the Worker, the dashboard and both clients.
|
|
278
|
+
*
|
|
279
|
+
* Reachable today? No. The Worker returns 409 rather than a second
|
|
280
|
+
* "approve" when an approval cannot be redeemed, so the re-check after a
|
|
281
|
+
* yes comes back allow, redact, or an error. This is the client refusing
|
|
282
|
+
* to depend on that, across a version boundary it does not control.
|
|
264
283
|
*/
|
|
265
284
|
private sendable;
|
|
266
285
|
guard<A, R>(tool: string, fn: (input: A) => Promise<R> | R, opts?: {
|
package/dist/index.js
CHANGED
|
@@ -96,6 +96,30 @@ export class SecureAIError extends Error {
|
|
|
96
96
|
* Keep the two in step: docs/api.md in the web repo is the other half.
|
|
97
97
|
*/
|
|
98
98
|
const DEFAULT_BASE = "https://secureai.one";
|
|
99
|
+
/**
|
|
100
|
+
* An approval off the wire, with the one field the wait loop depends on
|
|
101
|
+
* made a number.
|
|
102
|
+
*
|
|
103
|
+
* waitForApproval stops when now is past expiresAt. Handed a body without
|
|
104
|
+
* one, `Date.now() >= undefined` is a NaN comparison, which is false —
|
|
105
|
+
* so the loop never stops. Not an error, not a hang anybody can see from
|
|
106
|
+
* the outside: a guarded call polling a metered API every two seconds for
|
|
107
|
+
* as long as the process lives.
|
|
108
|
+
*
|
|
109
|
+
* The Python client already coerced this, defaulting a missing expiry to 0
|
|
110
|
+
* and so treating it as already expired. This is the same rule: anything
|
|
111
|
+
* that is not a finite number means expired, which refuses rather than
|
|
112
|
+
* waits. Today's Worker always sends one — buildApproval sets it from a
|
|
113
|
+
* bounded TTL — and the client should not be the reason a change there
|
|
114
|
+
* becomes an infinite loop in somebody's agent.
|
|
115
|
+
*/
|
|
116
|
+
function toApproval(raw) {
|
|
117
|
+
const expiresAt = Number(raw?.expiresAt);
|
|
118
|
+
return {
|
|
119
|
+
...raw,
|
|
120
|
+
expiresAt: Number.isFinite(expiresAt) ? expiresAt : 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
99
123
|
export class SecureAI {
|
|
100
124
|
apiKey;
|
|
101
125
|
baseUrl;
|
|
@@ -159,7 +183,7 @@ export class SecureAI {
|
|
|
159
183
|
/** One held action. */
|
|
160
184
|
async approval(id) {
|
|
161
185
|
const body = await this.request("GET", `/v1/approvals/${encodeURIComponent(id)}`);
|
|
162
|
-
return body.approval;
|
|
186
|
+
return toApproval(body.approval);
|
|
163
187
|
}
|
|
164
188
|
/** Everything waiting, for a reviewer's screen. */
|
|
165
189
|
async approvals(opts) {
|
|
@@ -170,7 +194,7 @@ export class SecureAI {
|
|
|
170
194
|
q.set("limit", String(opts.limit));
|
|
171
195
|
const qs = q.toString();
|
|
172
196
|
const body = await this.request("GET", `/v1/approvals${qs ? `?${qs}` : ""}`);
|
|
173
|
-
return body.approvals;
|
|
197
|
+
return (body.approvals ?? []).map(toApproval);
|
|
174
198
|
}
|
|
175
199
|
/**
|
|
176
200
|
* Wait for a person to decide.
|
|
@@ -248,7 +272,7 @@ export class SecureAI {
|
|
|
248
272
|
* What to hand the wrapped function.
|
|
249
273
|
*
|
|
250
274
|
* The API omits `input` only on a block, so on any other decision it is
|
|
251
|
-
* there. The question is what to do
|
|
275
|
+
* there. The question is what to do when it is not, and the answer is not
|
|
252
276
|
* "send what the caller had".
|
|
253
277
|
*
|
|
254
278
|
* On a redact, the caller's input is the one thing that must not go: it
|
|
@@ -259,8 +283,32 @@ export class SecureAI {
|
|
|
259
283
|
*
|
|
260
284
|
* On an allow, nothing was rewritten and the caller's own input is the
|
|
261
285
|
* correct thing to pass, so the fallback is right there and stays.
|
|
286
|
+
*
|
|
287
|
+
* ── Why this asks which decisions may send, rather than which may not ──
|
|
288
|
+
*
|
|
289
|
+
* Two decisions mean "this may go": allow and redact. Every other value,
|
|
290
|
+
* present or future, means it may not. Asked the other way round — refuse
|
|
291
|
+
* on block, refuse on approve, send otherwise — a decision added to the
|
|
292
|
+
* policy later is sent by a client that has never heard of it, and the
|
|
293
|
+
* agent is told the check passed.
|
|
294
|
+
*
|
|
295
|
+
* That is not hypothetical here. It is the bug this library already had:
|
|
296
|
+
* "approve" was added, `guard` compared against "block" and nothing else,
|
|
297
|
+
* and an action a policy said must wait for a person went immediately.
|
|
298
|
+
* The Python comment on that branch still records it. The same shape cost
|
|
299
|
+
* ten fixes across the Worker, the dashboard and both clients.
|
|
300
|
+
*
|
|
301
|
+
* Reachable today? No. The Worker returns 409 rather than a second
|
|
302
|
+
* "approve" when an approval cannot be redeemed, so the re-check after a
|
|
303
|
+
* yes comes back allow, redact, or an error. This is the client refusing
|
|
304
|
+
* to depend on that, across a version boundary it does not control.
|
|
262
305
|
*/
|
|
263
306
|
sendable(tool, verdict, original) {
|
|
307
|
+
if (verdict.decision !== "allow" && verdict.decision !== "redact") {
|
|
308
|
+
throw new SecureAIError(`Secure AI answered "${verdict.decision}" for ${tool}, which this ` +
|
|
309
|
+
`version does not know how to send safely. Nothing was sent. ` +
|
|
310
|
+
`Upgrade @secure-ai/guard.`, 502, "unknown_decision");
|
|
311
|
+
}
|
|
264
312
|
if (verdict.input !== undefined)
|
|
265
313
|
return verdict.input;
|
|
266
314
|
if (verdict.decision === "redact") {
|