AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Validation Boundary

skill-jagreehal-jagreehal-claude-skills-validation-boundary · by jagreehal

Validates untrusted input once at the system boundary with Zod schemas and branded types, so business functions trust their args by contract. Use when handling HTTP request bodies, query params, CLI args, queue messages, env vars, or third-party API responses; when defining Zod schemas or branded types; or when deciding where validation belongs in a TypeScript app.

— No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-jagreehal-jagreehal-claude-skills-validation-boundary

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-jagreehal-jagreehal-claude-skills-validation-boundary)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Validation Boundary? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Validation at the Boundary

Overview

Validation is a boundary concern. You check passports once at the border, not at every street corner. Untrusted input (HTTP bodies, query params, CLI args, queue messages, env vars, third-party responses) is parsed and rejected at the edge of the system. Everything inside the boundary trusts its types by contract.

External Input (HTTP, CLI, Queue, 3rd-party)  ;

2. Use Branded Types for Stronger Guarantees

const EmailSchema = z.string().email().brand();
const UserIdSchema = z.string().uuid().brand();

type Email = z.infer;   // string & { __brand: 'Email' }
type UserId = z.infer; // string & { __brand: 'UserId' }

// Now TypeScript prevents accidental raw strings
function sendEmail(to: Email, subject: string) { ... }

sendEmail("alice@example.com", "Hello");  // ERROR: string not assignable to Email
sendEmail(EmailSchema.parse("alice@example.com"), "Hello");  // OK
When to Use Branded Types vs Plain Types

| Use Branded Types | Use Plain Types | |-------------------|-----------------| | IDs that look alike (userId, orderId) | Internal-only types | | Security-sensitive values (tokens, keys) | Simple strings with no confusion risk | | Values that MUST go through validation | Prototyping / early development | | Cross-boundary data | Types only used in one function |

Rule of thumb: If mixing up two string parameters would cause a bug, brand them.

3. Validate at HTTP/Queue/CLI Boundaries

app.post('/users', async (req, res) => {
  // 1. Validate at the boundary
  const parsed = CreateUserSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).json(formatZodError(parsed.error));
  }

  // 2. Call business function with valid, typed data
  const user = await userService.createUser(parsed.data);

  return res.status(201).json(user);
});

4. Business Functions Trust the Contract

NO validation inside business functions. They trust args are already valid:

// CORRECT - No validation, trust the contract
async function createUser(
  args: CreateUserInput,  // Already validated!
  deps: CreateUserDeps
): Promise {
  const user = { id: crypto.randomUUID(), ...args };
  await deps.db.saveUser(user);
  return user;
}

// WRONG - Validation mixed with business logic
async function createUser(args: { name: string; email: string }, deps) {
  if (!args.name || args.name.length ;
};

function formatZodError(error: z.ZodError): ValidationErrorResponse {
  return {
    error: 'VALIDATION_FAILED',
    message: 'Request validation failed',
    issues: error.issues.map(issue => ({
      path: issue.path.join('.'),
      message: issue.message,
      code: issue.code,
    })),
  };
}

Two Layers of Validation

| Type | Where | What | Tool | |------|-------|------|------| | Schema Validation | Boundary | Shape, types, format, ranges | Zod | | Domain Validation | Business function | Business rules (email exists, has permission) | Database lookups |

// Schema validation (boundary)
const TransferSchema = z.object({
  fromAccount: z.string().uuid(),
  toAccount: z.string().uuid(),
  amount: z.number().positive(),
});

// Domain validation (business function)
async function validateTransfer(args: TransferInput, deps: TransferDeps) {
  const account = await deps.db.getAccount(args.fromAccount);
  if (account.balance  { page: 2, limit: 50 }

Partial Updates (PATCH)

const UpdateUserSchema = z.object({
  name: z.string().min(2).optional(),
  email: z.string().email().optional(),
});

Transforms

const CreatePostSchema = z.object({
  title: z.string().transform(s => s.trim()),
  slug: z.string().transform(s => s.toLowerCase().replace(/\s+/g, '-')),
});

Express Middleware

function validateBody(schema: z.ZodSchema) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);

    if (!result.success) {
      return res.status(400).json(formatZodError(result.error));
    }

    req.body = result.data;
    next();
  };
}

app.post('/users', validateBody(CreateUserSchema), async (req, res) => {
  const user = await userService.createUser(req.body);
  res.status(201).json(user);
});

Quick Reference

| Question | Answer | |----------|--------| | Where validate shape/format? | Boundary (Zod schema) | | Where validate business rules? | Business function | | Should fn(args, deps) validate args? | NO. Trust the contract | | Error for invalid input? | HTTP 400 (client error) |

Common Rationalizations

| Rationalization | Reality | |---|---| | "I'll just check the input inside the function too, to be safe" | Double validation means neither layer is authoritative and the function now has two jobs. Parse once at the boundary; trust the type after. | | "It came from our own database, but I'll validate it anyway" | Data that already crossed a boundary (or originated internally) is trusted. Re-validating it is noise that hides where the real boundary is. | | "The third-party API always returns the right shape" | External services are untrusted. They change, fail, and can return malicious or instruction-like content. Parse their responses like any other boundary input. | | "A plain string is fine for the user ID" | If two same-typed values can be swapped by mistake (userId vs orderId), brand them so the compiler catches the mix-up. | | "Throwing inside the business function is simpler than returning a Result for the bad-balance case" | Schema validation belongs at the boundary; business-rule failures (insufficient funds, not found) are expected outcomes: return them as result-types, don't throw. |

Red Flags

  • if (!args.email) or .length r.json()) result used without parsing its shape
  • Raw string parameters for IDs, tokens, or other easily-confused values
  • A Zod schema defined but only used for types, never .parse()d at the edge
  • Validation logic duplicated across multiple handlers instead of shared middleware/schema

Verification

After wiring up input handling:

  • [ ] Every external input is parsed with a Zod schema at the boundary
  • [ ] Business functions accept already-validated types and contain no shape checks
  • [ ] IDs/tokens that could be confused use branded types
  • [ ] Third-party responses are parsed before use
  • [ ] Invalid input returns a consistent error (HTTP 400 / VALIDATION_FAILED)
  • [ ] Business-rule failures are returned as Results, not thrown (see [result-types](../result-types/SKILL.md))
  • [ ] No re-validation of internal or database-sourced data

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.