Skip to content

feat: PHP SDK proof-of-concept (Laravel + Symfony)#409

Draft
jkbennemann wants to merge 9 commits intodmno-dev:mainfrom
jkbennemann:feat/php-sdk-poc
Draft

feat: PHP SDK proof-of-concept (Laravel + Symfony)#409
jkbennemann wants to merge 9 commits intodmno-dev:mainfrom
jkbennemann:feat/php-sdk-poc

Conversation

@jkbennemann
Copy link

@jkbennemann jkbennemann commented Mar 14, 2026

Closes #359

What

PoC for bringing varlock to PHP via a compiled manifest approach. varlock compile produces .varlock/manifest.json containing schema rules and resolver references (zero secrets). PHP SDKs read this at boot, validate env, resolve secrets via HTTP, and redact logs.

No exec(), no Node.js at runtime, no sidecar.

Packages

Package Path What it does
varlock/php-core packages/sdks/php-core/ Manifest loading, validation, type coercion, redaction, secret resolvers
varlock/laravel packages/sdks/php-laravel/ Service provider, artisan command, Monolog processor
varlock/symfony-bundle packages/sdks/php-symfony/ Bundle, Monolog processor

Quick start

# Start mock secret server (simulates 1Password Connect / Vault)
php examples/mock-secret-server.php &

# Laravel
cd examples/laravel-app && composer install
php artisan varlock:status          # shows resolved config, secrets redacted

# Symfony
cd examples/symfony-app && composer install
php -S localhost:8078 -t public &
curl localhost:8078/varlock/status   # JSON, secrets redacted

The .env files have empty values for all secrets. They're resolved from the HTTP API at boot.

How it works (short version)

  1. Framework loads .env into $_ENV (secrets are empty)
  2. VarlockBootstrap::load() reads the manifest, calls the secrets API for items with a resolve block, writes values back into $_ENV
  3. Framework parses config files — env('DB_PASSWORD') now returns the resolved secret
  4. Monolog processor redacts any sensitive value that appears in log output

The tricky part is boot timing — this must happen after Dotenv but before config parsing. The README (packages/sdks/README.md) explains how this works in both Laravel and Symfony, written for a JS audience.

What this needs from varlock core

A varlock compile command that emits manifest.json. The README has a TypeScript interface for the expected schema. Everything else is self-contained.

Why

AI coding agents can read .env files. With this approach, .env contains zero secrets — the manifest tells the SDK how to resolve them at runtime, not what they are. Secrets only exist in process memory.

jakob.bennemann added 9 commits March 14, 2026 08:57
Framework-agnostic core library for the compiled manifest approach.
Reads .varlock/manifest.json at boot, validates env vars against schema
rules, coerces string values to PHP types, and provides log redaction.

Key components:
- ManifestLoader: parses the JSON manifest
- Validator: checks required fields and type constraints
- TypeCoercer: converts strings to bool/int/float based on schema
- VarlockState: static singleton holding resolved values and sensitive map
- RedactionHelper: replaces sensitive values with [REDACTED]
- SecretResolverFactory: registry dispatching to plugin resolvers
- HttpSecretResolver: calls any HTTP secrets API with response caching
- EnvSecretResolver: reads from process env (Docker/K8s injection)
- CallbackSecretResolver: wraps user-provided closures
- ChainSecretResolver: tries resolvers in order with fallback

No framework dependencies — requires only PHP 8.2.
Laravel integration that hooks into the framework boot sequence via
afterBootstrapping(LoadEnvironmentVariables::class). This ensures
secrets resolved from external APIs are available BEFORE config files
call env(), solving the timing problem where config is parsed once
at boot with whatever values are in $_ENV at that moment.

- VarlockBootstrap: resolves secrets, validates, coerces types, populates env
- VarlockServiceProvider: registers artisan command and Monolog processor
- StatusCommand: `php artisan varlock:status` shows loaded vars with redaction
- RedactSensitiveProcessor: Monolog processor replacing secrets with [REDACTED]

Handles Laravel config caching gracefully — skips resolution when
bootstrap/cache/config.php exists since env vars are not read at runtime.
Symfony integration that bootstraps inside the runtime closure in
public/index.php, after Dotenv has loaded but before the kernel boots.

Unlike the Laravel integration, values are stored as strings only —
Symfony's env processors (%env(bool:APP_DEBUG)%) handle type casting
in config files.

- VarlockBootstrap: resolves secrets, validates, populates env
- VarlockBundle: registers Monolog processor via dependency injection
- RedactSensitiveProcessor: Monolog processor replacing secrets with [REDACTED]
The manifest demonstrates the compiled manifest format with resolve
blocks pointing sensitive items at an HTTP secrets API. Non-sensitive
items use defaults. No secret values appear in the manifest.

The mock server (mock-secret-server.php) simulates what 1Password
Connect, HashiCorp Vault, or any secrets HTTP API would return.
Serves on localhost:9777 for local development and demo purposes.
Demonstrates the full flow: empty .env (no secrets on filesystem),
secrets resolved from mock HTTP API at boot, log redaction working.

Integration points:
- bootstrap/app.php: afterBootstrapping hook for early env injection
- composer.json: path repositories pointing to local SDK packages
- .varlock/manifest.json: schema with resolve blocks for sensitive items
- routes/web.php: /varlock/status, /varlock/log-test, /varlock/validation-test

The .env file has APP_KEY= and DB_PASSWORD= (empty). These are resolved
from the mock secret server at runtime via HttpSecretResolver.
Demonstrates varlock in Symfony's boot model: secrets resolved inside
the runtime closure after Dotenv has loaded, before the kernel boots.

Integration points:
- public/index.php: VarlockBootstrap::load() in the runtime closure
- config/bundles.php: VarlockBundle registered for Monolog processor
- .varlock/manifest.json: schema with resolve blocks for sensitive items
- src/Controller/VarlockController.php: /varlock/status, /varlock/log-test

The .env follows Symfony convention (committed with empty placeholders).
Secrets are resolved from the mock HTTP API, never stored in .env files.
README.md covers the full PoC for a JS-focused audience:
- How .env works in PHP vs Node.js (Laravel and Symfony boot sequences)
- The compiled manifest approach and why PHP needs it
- Secret resolution architecture (HTTP, env, callback, chain resolvers)
- The AI agent safety argument (no secrets on filesystem)
- Running the demo end-to-end
- What varlock compile would need to generate
- Design decisions and extending to other languages

Gitignore additions: vendor/, composer.lock, framework-specific .env rules
following each framework's convention (Laravel .env gitignored, Symfony .env
committed with empty placeholders).
VarlockBootstrap::load() runs inside afterBootstrapping() — before
Laravel's config service, logger, or exception handler exist. Previously,
any failure (e.g. secret server unreachable) threw an exception that
Laravel tried to log, cascading into "Target class [config] does not
exist".

Now the bootstrap:
- Catches all exceptions itself and writes to STDERR with color codes
- Collects per-key resolution errors and shows them as warnings
- Still fails fast on validation (required vars missing) but with a
  clean message instead of a framework crash
- Shows a minimal HTML error for browser requests during development
@changeset-bot
Copy link

changeset-bot bot commented Mar 14, 2026

⚠️ No Changeset found

Latest commit: 2b2ceda

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@socket-security
Copy link

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

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedlaravel-vite-plugin@​2.1.01001008085100
Addedconcurrently@​9.2.19910010083100
Addedtailwindcss@​4.2.11001008498100
Added@​tailwindcss/​vite@​4.2.11001008998100
Addedaxios@​1.13.69710010096100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Support for Laravel Framework?

1 participant