@ory/amp 0.10.0 → 0.11.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
@@ -49,7 +49,7 @@ npx -y -p @ory/amp ory-amp uninstall # remove the delegate, plugin, and skills
49
49
 
50
50
  From any project where you'd like Ory authentication, inside Amp:
51
51
 
52
- 1. **Start a local Ory instance.** Ask Amp *"start the local Ory stack"* or run `/local-up`. A banner prints the seeded test user's email and password — note them.
52
+ 1. **Start a local Ory instance.** Ask Amp *"start the local Ory stack"* or invoke the `ory-local-up` skill. A banner prints the seeded test user's email and password — note them.
53
53
  2. **Scaffold Ory into your project.** Ask Amp *"add Ory auth to this app"* (the `ory-auth-setup` skill). It installs Ory Elements, wires the SDK, and generates the login / registration / recovery / settings pages, all targeting the local stack.
54
54
  3. **Sign in.** Start your app, visit the login page, and sign in with the seeded credentials. You now have a real Ory session backed by a real Ory stack — locally, offline, zero configuration.
55
55
 
@@ -59,7 +59,7 @@ Continue to [Agent security](#agent-security) when you're ready to enforce.
59
59
 
60
60
  Amp's Ory integration is a **hybrid** of two mechanisms:
61
61
 
62
- - **Blocking gate — a permission delegate helper (`ory-amp-permission`).** Registered in `amp.permissions`, Amp invokes it per tool call with the tool params on stdin and decides by **exit code**: `0` = allow, `1` = ask, `≥2` = reject (stderr is surfaced to the model). This is the only part that can block. Amp enforces a 10-second delegate timeout, so the Ory check fails open (allows) on slow or unreachable backends.
62
+ - **Blocking gate — a permission delegate helper (`ory-amp-permission`).** Registered in `amp.permissions`, Amp invokes it per tool call with the tool params on stdin and decides by **exit code**: `0` = allow, `1` = ask, `≥2` = reject (stderr is surfaced to the model). This is the only part that can block. Amp enforces a 10-second delegate timeout and treats a delegate that outlives it as a reject, so the delegate applies its own deadline to the Ory permission check (default 5 seconds, configurable via `ORY_AMP_CHECK_TIMEOUT_MS`) and fails open (allows) when the check is slow, hanging, or unreachable.
63
63
  - **Advisory in-process plugin (`.amp/plugins/ory.ts`).** Discovered by Amp's Bun runtime, it handles session-start auth (advisory — it cannot hard-block at session start) and post-tool audit tracing.
64
64
 
65
65
  The phases map as:
@@ -16,10 +16,11 @@
16
16
  * here a denial is exit 2 *plus a stderr message*, and stdout is unused.
17
17
  *
18
18
  * TIMEOUT: Amp enforces a 10-second delegate timeout. A delegate that does
19
- * not exit within 10s is treated as a reject, so every Ory call below
20
- * (session resolution + permission check) must complete well inside that
21
- * window; the fail-open arms below keep slow/unreachable Ory from wedging
22
- * the agent, but a hard 10s hang would still surface as a reject.
19
+ * not exit within 10s is treated as a reject, so a hanging Ory backend
20
+ * would otherwise surface as a block. To keep infra failures fail-open,
21
+ * the permission check below is raced against a local deadline (default
22
+ * 5000ms, override via ORY_AMP_CHECK_TIMEOUT_MS); on timeout the delegate
23
+ * logs a warning to stderr and allows (exit 0), well inside Amp's window.
23
24
  *
24
25
  * Fail-open: any error — parse failure, network error, rate limit, crash —
25
26
  * resolves to exit 0 (allow). The delegate must never wedge the agent.
@@ -35,6 +36,8 @@ export interface DelegateDecision {
35
36
  /** Reason to write to stderr (only set when rejecting). */
36
37
  reason?: string;
37
38
  }
39
+ /** Resolve the check deadline, overridable via ORY_AMP_CHECK_TIMEOUT_MS. */
40
+ export declare function resolveCheckTimeoutMs(): number;
38
41
  /** Pull the tool name out of the (undocumented) delegate payload. */
39
42
  export declare function extractToolName(input: AmpDelegateInput): string;
40
43
  /**
@@ -17,10 +17,11 @@
17
17
  * here a denial is exit 2 *plus a stderr message*, and stdout is unused.
18
18
  *
19
19
  * TIMEOUT: Amp enforces a 10-second delegate timeout. A delegate that does
20
- * not exit within 10s is treated as a reject, so every Ory call below
21
- * (session resolution + permission check) must complete well inside that
22
- * window; the fail-open arms below keep slow/unreachable Ory from wedging
23
- * the agent, but a hard 10s hang would still surface as a reject.
20
+ * not exit within 10s is treated as a reject, so a hanging Ory backend
21
+ * would otherwise surface as a block. To keep infra failures fail-open,
22
+ * the permission check below is raced against a local deadline (default
23
+ * 5000ms, override via ORY_AMP_CHECK_TIMEOUT_MS); on timeout the delegate
24
+ * logs a warning to stderr and allows (exit 0), well inside Amp's window.
24
25
  *
25
26
  * Fail-open: any error — parse failure, network error, rate limit, crash —
26
27
  * resolves to exit 0 (allow). The delegate must never wedge the agent.
@@ -29,6 +30,7 @@
29
30
  * and 10s timeout).
30
31
  */
31
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.resolveCheckTimeoutMs = resolveCheckTimeoutMs;
32
34
  exports.extractToolName = extractToolName;
33
35
  exports.decideToolPermission = decideToolPermission;
34
36
  const argus_1 = require("@ory/argus");
@@ -36,6 +38,44 @@ const types_js_1 = require("./types.js");
36
38
  function resolveNamespace() {
37
39
  return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
38
40
  }
41
+ /**
42
+ * Local deadline for the Ory permission check. Amp treats a delegate that
43
+ * does not exit within 10s as a REJECT, so a hanging backend must be cut
44
+ * off locally and resolved as allow (fail-open for infra failures). The
45
+ * default leaves ample headroom inside Amp's 10s window.
46
+ */
47
+ const DEFAULT_CHECK_TIMEOUT_MS = 5000;
48
+ /** Resolve the check deadline, overridable via ORY_AMP_CHECK_TIMEOUT_MS. */
49
+ function resolveCheckTimeoutMs() {
50
+ const raw = process.env.ORY_AMP_CHECK_TIMEOUT_MS;
51
+ if (raw) {
52
+ const parsed = Number(raw);
53
+ if (Number.isFinite(parsed) && parsed > 0)
54
+ return parsed;
55
+ }
56
+ return DEFAULT_CHECK_TIMEOUT_MS;
57
+ }
58
+ /** Sentinel resolved by the deadline arm of the race below. */
59
+ const CHECK_TIMED_OUT = Symbol("ory-amp-check-timeout");
60
+ /**
61
+ * Race a promise against the local check deadline. The timer is unref'd
62
+ * (and cleared once the race settles) so it never keeps the process alive.
63
+ */
64
+ async function raceCheckDeadline(work, timeoutMs) {
65
+ let timer;
66
+ try {
67
+ return await Promise.race([
68
+ work,
69
+ new Promise((resolve) => {
70
+ timer = setTimeout(() => resolve(CHECK_TIMED_OUT), timeoutMs);
71
+ timer.unref?.();
72
+ }),
73
+ ]);
74
+ }
75
+ finally {
76
+ clearTimeout(timer);
77
+ }
78
+ }
39
79
  /** Pull the tool name out of the (undocumented) delegate payload. */
40
80
  function extractToolName(input) {
41
81
  return input.tool ?? input.toolName ?? input.name ?? "unknown";
@@ -68,7 +108,11 @@ async function decideToolPermission(input, client) {
68
108
  const subject = (0, argus_1.resolveUserSubject)(client, sessionId ? `session:${sessionId}` : undefined);
69
109
  const subjectId = (0, argus_1.subjectLabel)(subject);
70
110
  try {
71
- const outcome = await (0, argus_1.gateToolCall)(client, {
111
+ // Race against a local deadline: core's checkPermission has no HTTP
112
+ // timeout, and Amp treats a delegate that outlives its 10s window as
113
+ // a reject — a hanging backend must fail OPEN, not closed.
114
+ const timeoutMs = resolveCheckTimeoutMs();
115
+ const raced = await raceCheckDeadline((0, argus_1.gateToolCall)(client, {
72
116
  harness: "amp",
73
117
  toolName,
74
118
  check: {
@@ -78,7 +122,13 @@ async function decideToolPermission(input, client) {
78
122
  ...subject,
79
123
  },
80
124
  spanAttributes: { toolName },
81
- });
125
+ }), timeoutMs);
126
+ if (raced === CHECK_TIMED_OUT) {
127
+ process.stderr.write(`[ory-agent] permission check for "${toolName}" timed out after ${timeoutMs}ms; allowing (fail-open)\n`);
128
+ client.logger.warn("delegate.check_timeout", { toolName, timeoutMs });
129
+ return { exitCode: types_js_1.AMP_DELEGATE_EXIT.ALLOW };
130
+ }
131
+ const outcome = raced;
82
132
  // Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
83
133
  // the user.interaction span is already recorded; allow so Amp can
84
134
  // surface the prompt to the user.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/amp",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Ory plugin for Amp (Sourcegraph's coding agent): a permission delegate that authorizes every tool call plus an in-process plugin for session auth and audit tracing",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ory.com",
@@ -67,7 +67,7 @@
67
67
  "!dist/**/*.tsbuildinfo"
68
68
  ],
69
69
  "dependencies": {
70
- "@ory/argus": "0.10.0"
70
+ "@ory/argus": "0.11.0"
71
71
  },
72
72
  "devDependencies": {
73
73
  "typescript": "^6.0.2",