pickle

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2026 License: MIT

README ΒΆ

Pickle πŸ₯’

The world's most secure web framework.

Pickle is a code generation framework for Go that makes secure applications trivial to build β€” for humans and AI alike. You write controllers, migrations, request classes, and middleware. Pickle generates plain, idiomatic Go. The output compiles to a single static binary with no runtime dependency on Pickle.

You write controllers.     Pickle generates models.
You write migrations.      Pickle generates query builders.
You write migrations.      Pickle generates a GraphQL API.
You write request classes.  Pickle generates validation + deserialization.
You write routes.go.       Pickle wires it all together.

The generated code is readable, debuggable, and grep-friendly. It's not magic. It's just code you didn't have to type.

Why "Most Secure"?

Every framework claims to care about security. Django has CSRF tokens. Rails has strong parameters. Spring has Security. These are best practices bolted onto runtime frameworks β€” they help, but they rely on developers remembering to use them correctly every time.

Pickle is different. It makes entire vulnerability classes structurally impossible, architecturally visible, or caught at build time before code ships.

Impossible by construction:

  • SQL injection β€” The generated query builder uses parameterized queries exclusively. There is no API for string interpolation. The unsafe path doesn't exist.
  • Mass assignment β€” Request structs define exactly which fields are accepted. If CreateUserRequest doesn't have a Role field, POSTing {"role": "admin"} does nothing. The model never sees unvalidated input.
  • Validation bypass β€” Controllers receive pre-validated, typed request structs. The generated binding layer runs validation before your code executes. There is no code path around it.
  • Encryption at rest β€” Columns marked .Encrypted() are transparently encrypted before storage and decrypted on read. Columns marked .Sealed() are write-only β€” they can be verified but never retrieved in plaintext. The query builder enforces both: no range queries on encrypted columns, no WHERE clauses on sealed columns.
  • Data tampering β€” Immutable and append-only tables are cryptographically hash-chained. Every row's row_hash includes the previous row's hash β€” tampering with any historical record breaks the chain. Periodic Merkle tree checkpoints give O(log n) inclusion proofs you can hand to an auditor.

Impossible by construction (RBAC and actions):

  • Ungated actions β€” every action requires a gate function. The generator refuses to produce output if a gate is missing. The action method is renamed to unexported in the compiled output, so it can only be called through the gated model method.
  • Role visibility leaks β€” column annotations (ComplianceSees(), SupportSees()) generate SelectFor(role) query scopes. Unknown roles see only Public() columns. Manages() roles see everything. Squeeze flags controllers that query role-annotated models without calling SelectFor*.
  • Audit trail gaps β€” every successful action execution writes an append-only audit row in the same database transaction as the action. Both succeed or both roll back. No action persists without its audit record.

Visible by convention:

  • Every endpoint, its middleware stack, and its grouping are in one file: routes/web.go. A missing Auth, LoadRoles, or RequireRole is immediately obvious β€” to you and to any AI reviewing your code. One file, entire API surface, 30-second security review.

Caught at build time by Squeeze:

  • IDOR (Insecure Direct Object Reference) β€” The security industry has accepted IDOR as a manual-testing problem. No tool, framework, or scanner claims to detect all IDORs. Squeeze does. It traces route β†’ middleware β†’ controller β†’ query and verifies the chain is scoped by owner. This is possible because Pickle owns all three layers: migrations define ownership columns, the router defines middleware, controllers use generated query scopes. No other framework has this because no other framework was designed to make its own security properties statically analyzable.
  • Data leakage, unbounded queries, missing rate limits, enum validation, UUID panics, missing required fields β€” all caught before deployment. See Squeeze below.

Standard security tooling works out of the box. Generated code is plain Go β€” go vet, gosec, staticcheck, Snyk, and Semgrep work with zero configuration. No framework abstractions to unwrap. Security scanners see exactly what runs in production.

Squeeze: Make Sure Nothing's Oozing

pickle squeeze is static security analysis that understands your framework β€” routes, middleware, migrations, request classes β€” and catches vulnerabilities that generic linters can't see.

pickle squeeze              # Run full validation
pickle squeeze --hard       # Strict mode: warnings become failures
πŸ₯’ Squeezing your pickle...
πŸ₯’ Your pickle is crunchy.

If something's wrong, Squeeze tells you exactly where:

πŸ₯’ Squeezing your pickle...

  app/http/controllers/post_controller.go
    line 28 [ownership_scoping] PUT /api/posts/:id β€” query not scoped by owner (IDOR)

πŸ₯’ Your pickle is oozing. 1 error(s), 0 warning(s)
Rules
Rule Severity What it catches
ownership_scoping error Write routes (PUT/PATCH/DELETE) behind auth that don't scope queries by owner β€” IDOR vulnerabilities
read_scoping error Read routes (GET) behind auth that don't scope queries by owner β€” data leakage
public_projection error Unauthenticated routes returning model data without .Public() β€” leaks sensitive fields
unbounded_query error .All() without .Limit() β€” denial-of-service vector
rate_limit_auth error Auth endpoints (login, register) without rate limiting middleware
enum_validation error Status/role/type fields without oneof= validation β€” accepts arbitrary values
uuid_error_handling error uuid.MustParse() on user input β€” panics crash the server
required_fields error Create() calls missing NOT NULL fields β€” database rejects the insert
no_printf warning fmt.Print* in controllers β€” use structured logging
param_mismatch error Route parameters (:id) with no corresponding ctx.Param() call, or vice versa
auth_without_middleware error ctx.Auth() called in a controller without auth middleware on the route
immutable_raw_update error Raw UPDATE on an immutable or append-only table β€” use the query builder
immutable_raw_delete error Raw DELETE on an immutable table without SoftDeletes()
immutable_timestamps error t.Immutable() + t.Timestamps() on the same table β€” timestamps are derived from UUID v7
integrity_hash_override error Raw SQL setting row_hash or prev_hash β€” these are computed by the query builder
encrypted_column_range error Range/comparison scopes (GT, LT, Between) on .Encrypted() columns β€” ciphertext ordering is meaningless
sealed_column_where error Any WHERE clause on a .Sealed() column β€” sealed data cannot be queried
encrypted_column_order_by error ORDER BY on an .Encrypted() column β€” ciphertext sort order is random
encrypted_sealed_conflict error Column marked both .Encrypted() and .Sealed() β€” pick one
encrypted_missing_key_config error .Encrypted() columns exist but no encryption key is configured
stale_role_annotation warning Migration uses XxxSees() for a role removed via policy
unknown_role_annotation error Migration uses XxxSees() for a role that has never been defined
role_without_load error RequireRole() used but LoadRoles not in middleware chain
default_role_missing error Policies exist but no role has .Default(), or multiple do
ungated_action error Action exists with no corresponding gate
direct_execute_call error Action method called directly instead of through the gated model method
scope_builder_leak error ScopeBuilder referenced outside database/scopes/
query_builder_in_scope error XxxQuery referenced inside database/scopes/

No pickle ships without being squeezed first.

# .github/workflows/squeeze.yml
- name: Squeeze the pickle
  run: pickle squeeze --hard
Built for AI

Pickle isn't just secure β€” it's the most AI-friendly backend framework you can use. Every convention serves two audiences: the developer who needs to ship, and the AI model that needs to help.

A functioning Pickle app is ~2,000 tokens of source. Controllers are pure business logic β€” no boilerplate to read past. Request structs are self-documenting API contracts. Migrations are the single source of truth for schema. An AI model doesn't need to parse framework wiring to understand what your endpoint does.

Pickle ships an MCP server that gives AI models queryable access to your project's structure β€” without dumping source files into context.

pickle schema:show transfers    β†’ exact table structure with visibility annotations
pickle routes:list              β†’ every endpoint, middleware, request class
pickle roles:list               β†’ all RBAC roles with permissions
pickle roles:show admin         β†’ single role with column visibility and action grants
pickle graphql:list             β†’ exposed GraphQL models with operations
pickle make:controller          β†’ scaffold via tooling, not by writing boilerplate

The model doesn't read your code. It queries your constraints. It discovers what fields exist, what's validated, what middleware protects each route, what relationships are defined β€” all through structured tool calls. Even lightweight models produce code that respects your schema, validation rules, and security boundaries.

This is why Pickle can do in 5 minutes what takes other frameworks hours. It's not faster typing. It's less context required to make correct decisions.


Getting Started: Unboxing Your First Pickle

See the Getting Started guide to create your first Pickle project.

Documentation

Topic Description
Getting Started Create your first Pickle project
Controllers Handling requests and returning responses
Middleware Auth, rate limiting, and request pipeline
Requests Validation and deserialization
Migrations Database schema as code
Views Database views with computed columns
Router Route definitions and groups
Context The request context object
Response Building HTTP responses
QueryBuilder Typed database queries
Config Application configuration
Commands CLI commands reference
Tickle The preprocessor pipeline
Squeeze Static security analysis
GraphQL Auto-generated GraphQL API from migrations
Cron Jobs Scheduled background tasks
Encryption Encryption at rest and sealed columns
RBAC Role-based access control, column visibility, role-aware queries
Policies Role policies and GraphQL exposure policies
Actions Gated actions, scopes, and audit trails
Ledger Example Immutable tables, append-only tables, DB permissions
Immutable Tables & Cryptographic Integrity

Financial records, audit logs, compliance data β€” anything where history matters. Declare t.Immutable() or t.AppendOnly() in your migration and Pickle enforces it at every layer.

m.CreateTable("transactions", func(t *Table) {
    t.AppendOnly()
    t.UUID("account_id").NotNull().ForeignKey("accounts", "id")
    t.String("type", 20).NotNull()
    t.Decimal("amount", 18, 2).NotNull()
    t.String("currency", 3).NotNull()
})

What you get:

Mutable Immutable Append-Only
DSL t.Timestamps() t.Immutable() t.AppendOnly()
Create() INSERT INSERT INSERT
Update() UPDATE INSERT new version Not generated
Delete() DELETE INSERT with deleted_at* Not generated
Hash chain No Yes Yes
Merkle proofs No Yes Yes
DB permissions needed SELECT, INSERT, UPDATE, DELETE SELECT, INSERT SELECT, INSERT

* Only with t.SoftDeletes(). Without it, Delete() is not generated β€” immutable tables without soft deletes have no deletion concept.

Developer code is identical to mutable tables:

// Create β€” hash chain extended automatically
transfer := &models.Transfer{CustomerID: id, Amount: amount, Status: "pending"}
models.QueryTransfer().Create(transfer)

// Read β€” always returns the latest version, transparently
transfer, _ := models.QueryTransfer().WhereID(id).First()

// Update β€” inserts a new version, old version preserved forever
transfer.Status = "completed"
models.QueryTransfer().Update(transfer)

// Full history β€” opt-in only
versions, _ := models.QueryTransfer().WhereID(id).AllVersions().All()

Cryptographic verification:

// Verify the full hash chain β€” O(n), run as a periodic audit
err := models.QueryTransaction().VerifyChain()

// Create a Merkle checkpoint β€” O(n) within the checkpoint window
cp, _ := models.QueryTransaction().Checkpoint()

// Generate an inclusion proof for an auditor β€” O(log n)
proof, _ := models.QueryTransaction().Proof(transaction)
ok := models.VerifyProof(proof) // pure function, no DB needed

Every row is chained to its predecessor via SHA-256. Merkle tree checkpoints roll the chain into a binary tree for efficient verification. Tampering with any historical row breaks the chain β€” detectable by VerifyChain() and provable via VerifyProof().

Three layers of enforcement: schema DSL (no unsafe methods generated), Go compiler (can't call what doesn't exist), database permissions (SELECT + INSERT only). Any one is sufficient. All three together means you can prove it to an auditor.

Cron Jobs

Schedule recurring tasks with pickle make:job. Jobs run inside your compiled binary β€” no external cron daemon needed. Define the schedule, write the logic, and Pickle wires it into the app lifecycle. See the Cron Jobs docs for details.

The Stack

Migrations β†’ Models β†’ Query Builders β†’ Controllers β†’ Routes
Policies   β†’ Roles  β†’ Gates          β†’ Actions     β†’ Audit Trail
     ↑ single source of truth              ↑ pure intent

Everything flows from migrations. Everything is queryable via MCP. Everything is verifiable via Squeeze. The generated output is plain Go with zero dependency on Pickle.

Contributing

Pickle is open to contributions. Here's how to get started:

git clone https://github.com/shortontech/pickle.git
cd pickle
go run ./pkg/tickle/cmd/                                        # tickle your pickle
go build ./...                                                   # build
go run ./cmd/pickle/ generate --project ./testdata/basic-crud/   # pickle the test app
go run ./cmd/pickle/ squeeze --project ./testdata/basic-crud/    # squeeze it
go test ./...                                                    # test

Tickle-generated embeds and testdata output are gitignored. You generate them locally.

Before submitting a PR:

  1. Run go run ./pkg/tickle/cmd/ β€” always, not just if you think you changed something
  2. Run go run ./cmd/pickle/ generate --project ./testdata/basic-crud/
  3. Run go run ./cmd/pickle/ squeeze --project ./testdata/basic-crud/ β€” must pass clean
  4. Run go test ./... β€” all tests must pass

Guidelines:

  • Generated files (*_gen.go) are never edited by hand. Change the source in pkg/cooked/, pkg/schema/, or the generator, then regenerate.
  • Squeeze rules should have zero false positives. If a rule fires, it should be a real problem. Noisy rules get disabled by users and stop providing value.
  • Security is the priority. If a change weakens any security guarantee β€” even for convenience β€” it won't be merged.
  • Keep the dependency list minimal. Pickle's output has zero dependency on Pickle. New runtime dependencies need strong justification.

Expressive DX. Go binary. No runtime. πŸ₯’

Directories ΒΆ

Path Synopsis
cmd
pickle command
pkg
mcp
tickle/cmd command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL