mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
fix: enforce assigned identity in managed cloud environments
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
aa69c14e0b
commit
7b0265da40
3 files changed
+45
No files matched your search
@@ -44,6 +44,13 @@ before its next turn. Private values are excluded from invocation metadata and
|
||||
execution configuration; output and failure diagnostics redact known private
|
||||
material. The run-log redactor handles split stdout/stderr chunks independently.
|
||||
|
||||
Managed remote providers, including Cursor Cloud, receive the same private key
|
||||
as managed local processes. Selecting a runtime therefore trusts its host with
|
||||
the agent's persistent identity: the agent and its host can retain the key and
|
||||
sign outside Paperclip. Encryption at rest protects database storage, not an
|
||||
executing runtime. Move an agent only between runtime hosts trusted with that
|
||||
identity.
|
||||
|
||||
Independently hosted HTTP and gateway agents, including Runner's API-hosted Claude Managed and AWS AgentCore providers, do not receive private keys in v1.
|
||||
Their new agent records still get stored identities; their existing records stay
|
||||
unprovisioned until a supported managed run.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { generateKeyPairSync, sign, verify } from "node:crypto";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AdapterExecutionContext } from "@paperclipai/adapter-utils";
|
||||
import { createPromptContextFixture } from "@paperclipai/adapter-utils/test-fixtures/prompt-context";
|
||||
@@ -266,6 +267,39 @@ describe("cursor_cloud execute", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("delivers the assigned identity to the managed cloud environment without exposing it in prompts or metadata", async () => {
|
||||
const keys = generateKeyPairSync("ed25519");
|
||||
const identity = {
|
||||
keyId: "sha256:assigned-identity",
|
||||
publicKeyPem: keys.publicKey.export({ type: "spki", format: "pem" }).toString(),
|
||||
privateKeyPem: keys.privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
||||
};
|
||||
const sdkAgent = createMockSdkAgent();
|
||||
createMock.mockResolvedValue(sdkAgent);
|
||||
const ctx = createContext({ agentIdentity: identity });
|
||||
ctx.config.env = {
|
||||
CURSOR_API_KEY: "cursor-secret",
|
||||
PAPERCLIP_AGENT_KEY_ID: "configured-override",
|
||||
PAPERCLIP_AGENT_PRIVATE_KEY: "configured-override",
|
||||
paperclip_agent_private_key: "configured-override",
|
||||
};
|
||||
|
||||
await execute(ctx);
|
||||
|
||||
const env = createMock.mock.calls[0]?.[0]?.cloud?.envVars;
|
||||
expect(env).toMatchObject({
|
||||
PAPERCLIP_AGENT_KEY_ID: identity.keyId,
|
||||
PAPERCLIP_AGENT_PUBLIC_KEY: identity.publicKeyPem,
|
||||
PAPERCLIP_AGENT_PRIVATE_KEY: identity.privateKeyPem,
|
||||
});
|
||||
expect(env).not.toHaveProperty("paperclip_agent_private_key");
|
||||
const challenge = Buffer.from("managed cloud identity");
|
||||
expect(verify(null, challenge, identity.publicKeyPem,
|
||||
sign(null, challenge, env.PAPERCLIP_AGENT_PRIVATE_KEY))).toBe(true);
|
||||
const privateBody = identity.privateKeyPem.split("\n")[1];
|
||||
expect(JSON.stringify([ctx.meta, ctx.logs, sdkAgent.send.mock.calls])).not.toContain(privateBody);
|
||||
});
|
||||
|
||||
it("omits empty environment values while preserving nonempty values exactly", async () => {
|
||||
createMock.mockResolvedValue(createMockSdkAgent());
|
||||
const ctx = createContext();
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
asString,
|
||||
buildPaperclipEnv,
|
||||
buildRuntimeToolsEnv,
|
||||
isForbiddenConfigEnvKey,
|
||||
joinPromptSections,
|
||||
parseObject,
|
||||
readPaperclipIssueWorkModeFromContext,
|
||||
@@ -59,6 +60,7 @@ function asStringEnvMap(value: unknown): Record<string, string> {
|
||||
const parsed = parseObject(value);
|
||||
const env: Record<string, string> = {};
|
||||
for (const [key, entry] of Object.entries(parsed)) {
|
||||
if (isForbiddenConfigEnvKey(key)) continue;
|
||||
if (typeof entry === "string") {
|
||||
env[key] = entry;
|
||||
} else if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
||||
@@ -108,6 +110,8 @@ function buildWakeEnv(ctx: AdapterExecutionContext, configEnv: Record<string, st
|
||||
const { runId, agent, context, authToken } = ctx;
|
||||
const env: Record<string, string> = {
|
||||
...configEnv,
|
||||
// The selected managed cloud host is trusted with this persistent identity,
|
||||
// just like a managed local host (see doc/AGENT-IDENTITY.md).
|
||||
...buildPaperclipEnv(agent, ctx.agentIdentity),
|
||||
...buildRuntimeToolsEnv(ctx.runtimeTools),
|
||||
PAPERCLIP_RUN_ID: runId,
|
||||
|
||||
Reference in new issue
Block a user