Skip to content

feat(examples): add LangChain createAgent guardrails example - #199

Open
davidmytton wants to merge 6 commits into
mainfrom
david/cursor/langchain-agent-ce4051
Open

feat(examples): add LangChain createAgent guardrails example#199
davidmytton wants to merge 6 commits into
mainfrom
david/cursor/langchain-agent-ce4051

Conversation

@davidmytton

@davidmytton davidmytton commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Adds examples/langchain-agent: a LangChain JS createAgent support agent protected by Arcjet Guard, alongside the mastra-agent / langgraph-agent / claude-agent siblings. Registered in the root README and scripts/prepare-to-publish.ts.

This is createAgent + wrapToolCall, not LangGraph's StateGraph + ToolNode (that is langgraph-agent). Docs slug is /guards/langchain-js/.

// Authored tool → guardTool. DENY returns a plain ArcjetDenialResult;
// createAgent's baseHandler wraps it in a success ToolMessage.
const lookupOrder = guardTool(arcjet, tool(handler, { name: "lookup_order", schema }), {
  action: "order.looked-up",
  onGuardError: "deny",
  rules: ({ orderId, note }) => [
    lookupLimit({ key: `order:${orderId}`, requested: 1 }),
    ...(note ? [detectPii(note)] : []),
  ],
});

// Unwrapped / MCP-like tool → guardMiddleware. wrapToolCall DENY returns a
// real ToolMessage without calling handler. Branded tools are skipped.
const agent = createAgent({
  model,
  tools: [lookupOrder, notifyWarehouse],
  middleware: [guardMiddleware(arcjet, { onGuardError: "deny", rules })],
});

// No guardInbound: screen before invoke, and act on both outcomes.
const decision = await arcjet.guard({
  label: "message.received",
  rules: [detectInjection(text)],
  ...langchainContext(config),
});
if (decision.conclusion === "DENY" || decision.hasFailedOpen()) return blocked();

await agent.invoke({ messages }, { configurable: { thread_id: conversationId } });

The two denial envelopes stay distinct: guardTool returns a plain object, wrapToolCall must return a real ToolMessage (a bare object crashes the messages reducer). Neither sets status: "error" and neither throws, so denials are read as arcjetDenied on the payload. humanInTheLoopMiddleware is HITL, not a policy gate — it appears only as a comment, and nothing denies in afterModel. thread_id is caller-owned and never minted.

Vendor pin. @arcjet/guard/langchain/v1 merged in arcjet-js#6248 (c49abcc1) but is not on npm yet, so @arcjet/guard is pinned to file:./vendor/arcjet-guard, same as langgraph-agent / claude-agent. SHA is in vendor/SOURCE.txt. Draft until that ships.

Verified. biome lint, tsc --noEmit, npm audit, and npm ci all clean. Behaviour exercised through a real createAgent loop with FakeToolCallingModel: inbound fail-closed, both DENY envelopes with the handler never running, ALLOW still reaching it, and a branded tool guarded exactly once. Also asserted rules receives real tool args — otherwise the rate-limit key would silently be order:undefined and the PII scan would never run.

Open in Web Open in Cursor 

cursoragent and others added 4 commits August 24, 2026 17:45
Add a standalone examples/langchain-agent matching eve/mastra/langgraph/claude.
Pins unpublished @arcjet/guard/langchain/v1 from arcjet-js@ce4051a6
(david/cursor/guard-langchain-v1-f9b3) via file:./vendor/arcjet-guard.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
Re-vendor unpublished @arcjet/guard/langchain/v1 from
8005e9a17d63bf051bdda88b7f307b940b7b15ca and slim the example around
createAgent, the two denial envelopes, and caller-owned thread_id.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
Rebuild @arcjet/guard/langchain/v1 from main @ c49abcc1. The
langchain/v1 subpath is still unpublished on npm, so keep the
file:./vendor/arcjet-guard pin.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
Drop the model parameter and AgentModel alias left over from verification,
use import type for BaseMessage (biome useImportType), derive the LangChain
context once per request, and split the inbound DENY and fail-open branches
back apart so each reads on its own.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
@socket-security

socket-security Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​langchain/​openai@​1.2.88310010097100
Addednpm/​langchain@​1.5.10991009496100
Addednpm/​@​langchain/​core@​1.2.910010010096100

View full report

@socket-security

socket-security Bot commented Aug 24, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

Ignoring alerts on:

  • npm/@langchain/langgraph-checkpoint@1.1.5
  • npm/@langchain/openai@1.2.8
  • npm/langsmith@0.9.0

View full report

@davidmytton
davidmytton marked this pull request as ready for review August 24, 2026 20:26

@arcjet-review arcjet-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arcjet Review — 🟡 Medium Risk

Decision: Needs Review

Rationale: Author explicitly marks the PR as Draft pending a published @arcjet/guard release to replace the vendored file: dependency, so this is not ready to merge on that ground alone. Beyond the vendor-pin gate, the change is a new self-contained example (mirrors existing langgraph-agent / claude-agent siblings), introduces a new package.json and Dockerfile (dependency-changes and ci-cd-changes triggers), and ships an intentionally unauthenticated /api/agent demo endpoint. Diff size (>1000 lines) is dominated by the vendored README and SKILL.md rather than executable code. Code-level review of index.ts / lib/agent.ts / lib/arcjet.ts shows input validation, fail-closed defaults on the guard, correct two-envelope denial handling, and no hardcoded secrets — but a human should confirm the vendored dist matches the recorded SHA and repin before merge.

Summary of Changes

Adds examples/langchain-agent, a new standalone LangChain JS createAgent example protected by Arcjet Guard, with an unauthenticated demo HTTP server, a Dockerfile, and a vendored @arcjet/guard build (file:./vendor/arcjet-guard pinned to arcjet/arcjet-js c49abcc1, pending npm release). Registered in the root README and scripts/prepare-to-publish.ts.

Escalation Triggers

  • Dependency Changes: New examples/langchain-agent/package.json with langchain, @langchain/core, @langchain/openai, zod, and a file:./vendor/arcjet-guard dependency for an unreleased subpath.
  • CI/CD Pipeline: New examples/langchain-agent/Dockerfile (author notes it is unverified because no Docker daemon is available in the devcontainer).

Security Flags

  • [LOW] Authentication & Authorization (examples/langchain-agent/index.ts:65): The /api/agent endpoint is unauthenticated and the server binds to 0.0.0.0. This is explicitly documented as a local demo pattern (matching sibling examples) and the README warns against deploying without adding auth/rate limiting, so it is intended behaviour — flagging so a human reader can confirm the demo scope is acceptable for this repo.
  • [MEDIUM] Vulnerable Components / Supply Chain (examples/langchain-agent/package.json:29): @arcjet/guard is pinned to a vendored directory (file:./vendor/arcjet-guard) built from arcjet/arcjet-js@c49abcc1 rather than a published npm release. The recorded SHA in vendor/SOURCE.txt should be re-verified against the vendored dist/ before merge, and the pin swapped to the published version once shipped. The Files Changed list does not include vendor/arcjet-guard/dist/**, so a human should confirm the dist/ tree is present in the branch and matches the SHA the author claims to have byte-compared.
  • [LOW] Insufficient Logging / Error Handling (examples/langchain-agent/index.ts:78): The /api/agent catch block echoes error.message back to the client with status 500 (e.g. an 'AI_GATEWAY_API_KEY is required' string), which can leak internal configuration hints. Acceptable for a local demo but worth flagging.

Review Focus Areas

Notes

Diff exceeds the 1000-line size threshold, but the bulk is the vendored @arcjet/guard README (~2100 lines) and SKILL.md (~300 lines). Executable example code (index.ts, lib/agent.ts, lib/arcjet.ts, index.html) is well under 800 lines and reads cleanly. vendor/arcjet-guard/dist/** is referenced by package.json's main/types/exports but does not appear in the Files Changed list — either the path filter excluded it or the dist is missing; a human should confirm on the branch.

Path filtering: 77 files excluded by ignore paths. 22 of 99 files included in review.

Review: 2abe7c2f | Model: anthropic/claude-opus-4-7 | Powered by Arcjet Review

@davidmytton

Copy link
Copy Markdown
Collaborator Author

@SocketSecurity ignore npm/langsmith@0.9.0

@davidmytton
davidmytton enabled auto-merge August 24, 2026 21:05
@arcjet-review arcjet-review Bot added needs review Awaiting human review labels Aug 24, 2026

@arcjet-rei arcjet-rei left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to vendor the SDK in the GH tree, or create a build job to bootstrap a vendor directory when the build is run on the example on download? The way this is set up right now, a very small percentage of the code is actually the example, and the rest is vendored SDK code.

Comment thread examples/langchain-agent/.devcontainer/devcontainer.json Outdated

Copy link
Copy Markdown
Collaborator Author

It's vendored temporarily until we do the SDK release. We can remove it all afterwards

@davidmytton
davidmytton disabled auto-merge August 25, 2026 20:29
Per PR review feedback — the devcontainer adds dependency surface
without value to end users.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
@arcjet-review arcjet-review Bot removed the needs review Awaiting human review label Aug 25, 2026
@davidmytton

Copy link
Copy Markdown
Collaborator Author

@SocketSecurity ignore npm/@langchain/openai@1.2.8

@arcjet-review arcjet-review Bot added the needs review Awaiting human review label Aug 25, 2026
@davidmytton
davidmytton enabled auto-merge August 25, 2026 20:45

@arcjet-rei arcjet-rei left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have a published version of the SDK that includes the LangChain helplers, should this be regenerated to not vendor the SDK?

Drop the vendored arcjet-js build now that @arcjet/guard/langchain/v1
ships on npm. Refresh lockfile and align Dockerfile/gitignore with other
published examples.

Co-authored-by: David Mytton <davidmytton@users.noreply.github.com>
@arcjet-review arcjet-review Bot removed the needs review Awaiting human review label Aug 30, 2026
@davidmytton

Copy link
Copy Markdown
Collaborator Author

Now that we have a published version of the SDK that includes the LangChain helplers, should this be regenerated to not vendor the SDK?

Yep - removed in c04c433

@davidmytton

Copy link
Copy Markdown
Collaborator Author

@SocketSecurity ignore npm/@langchain/langgraph-checkpoint@1.1.5

@arcjet-review arcjet-review Bot added the needs review Awaiting human review label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review Awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants