config(git,claude,ssh): templated gitconfig; claude settings/commands/hooks/skills; ssh config

gitconfig: name/email from chezmoi data, gh credential helper by PATH
lookup (portable to darwin). claude: no credentials, history, projects,
sessions, or plugin caches. ssh config as private_ (0600).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Prad NukalaandClaude Fable 5 committed 2026-08-09 14:53:46 -04:00
1 parent 3c691e8dc5
commit 20891f3910
476 files changed
+59086 -7

No files matched your search

@@ -0,0 +1,29 @@
/**
* Error formatting + helpers. Keep error responses consistent with
* Cloudflare's documented siteverify shape so callers can use one handler.
*/
import type { ErrorCode, SiteverifyResponse } from './types.js';
export class SiteverifyError extends Error {
constructor(
public readonly status: number,
public readonly code: ErrorCode,
message: string,
) {
super(message);
this.name = 'SiteverifyError';
}
}
/** Build a siteverify-shaped response for a Worker-level failure. */
export function errorResponse(code: ErrorCode, durationMs: number, workerVersion: string): SiteverifyResponse {
return {
success: false,
'error-codes': [code],
_worker: {
duration_ms: durationMs,
worker_version: workerVersion,
},
};
}
@@ -0,0 +1,199 @@
/**
* turnstile-siteverify — the managed siteverify backend for Turnstile Spin.
*
* A drop-in implementation of Cloudflare Turnstile server-side validation
* built entirely on Workers. Customers deploy this Worker into their account
* via Turnstile Spin (AI agent or Deploy-to-Cloudflare button) and point
* their frontend at it.
*
* Accepts both `application/x-www-form-urlencoded` and `application/json`
* bodies. Forwards the token to Cloudflare's siteverify endpoint using a
* secret stored as a Worker secret. Emits structured logs visible in Workers
* Observability.
*
* Endpoints:
* POST / siteverify proxy (main entry point)
* POST /siteverify alias of POST /
* GET /health health check, returns {ok: true, version}
* GET / same as /health
* GET /post-deploy the post-deploy form (served from public/)
*/
import { SiteverifyError, errorResponse } from './errors.js';
import { logSiteverify } from './observability.js';
import { validate } from './validate.js';
import type { Env, SiteverifyRequest } from './types.js';
export const WORKER_VERSION = '1.0.0';
function corsHeaders(env: Env): Record<string, string> {
return {
'Access-Control-Allow-Origin': env.ALLOWED_ORIGIN || '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Idempotency-Key',
'Access-Control-Max-Age': '86400',
Vary: 'Origin',
};
}
function jsonResponse(body: unknown, status: number, env: Env): Response {
return new Response(JSON.stringify(body, null, 2), {
status,
headers: {
'Content-Type': 'application/json; charset=utf-8',
...corsHeaders(env),
},
});
}
async function parseBody(request: Request): Promise<SiteverifyRequest> {
const contentType = request.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
const body = (await request.json()) as Record<string, unknown>;
return {
// `response` is accepted for compatibility with code migrated from reCAPTCHA/hCaptcha,
// whose backends POST {secret, response, remoteip}. Spin's Worker holds the secret,
// so callers should send {token} or {cf-turnstile-response}; we accept {response}
// too so migrated backends work without backend code changes.
token: String(body.token ?? body['cf-turnstile-response'] ?? body.response ?? ''),
remoteip: body.remoteip ? String(body.remoteip) : undefined,
idempotency_key: body.idempotency_key ? String(body.idempotency_key) : undefined,
};
}
if (contentType.includes('application/x-www-form-urlencoded') || contentType.includes('multipart/form-data')) {
const form = await request.formData();
return {
token: String(form.get('token') ?? form.get('cf-turnstile-response') ?? form.get('response') ?? ''),
remoteip: form.get('remoteip') ? String(form.get('remoteip')) : undefined,
idempotency_key: form.get('idempotency_key') ? String(form.get('idempotency_key')) : undefined,
};
}
throw new SiteverifyError(
415,
'invalid-content-type',
`Unsupported Content-Type: ${contentType || '(missing)'}. Use application/json or application/x-www-form-urlencoded.`,
);
}
async function handleSiteverify(request: Request, env: Env): Promise<Response> {
const startedAt = Date.now();
let parsed: SiteverifyRequest;
try {
parsed = await parseBody(request);
} catch (err) {
if (err instanceof SiteverifyError) {
const body = errorResponse(err.code, Date.now() - startedAt, WORKER_VERSION);
logSiteverify('error', {
durationMs: Date.now() - startedAt,
errorCodes: [err.code],
remoteipPresent: false,
idempotencyKeyPresent: false,
});
return jsonResponse(body, err.status, env);
}
// Body parse failure (malformed JSON, etc.)
const body = errorResponse('bad-request', Date.now() - startedAt, WORKER_VERSION);
logSiteverify('error', {
durationMs: Date.now() - startedAt,
errorCodes: ['bad-request'],
remoteipPresent: false,
idempotencyKeyPresent: false,
});
return jsonResponse(body, 400, env);
}
// Fall back to CF-Connecting-IP if customer didn't pass remoteip explicitly.
const remoteip = parsed.remoteip ?? request.headers.get('CF-Connecting-IP') ?? undefined;
const requestForValidate: SiteverifyRequest = { ...parsed, remoteip };
try {
const { response, upstreamStatus, durationMs } = await validate(requestForValidate, env, WORKER_VERSION);
logSiteverify(response.success ? 'ok' : 'fail', {
durationMs,
upstreamStatus,
errorCodes: response['error-codes'],
cdataValue: response.cdata,
remoteipPresent: !!remoteip,
idempotencyKeyPresent: !!parsed.idempotency_key,
});
// Per spec, return 200 even on validation failure — callers should check
// response.success, not the HTTP status. (Matches Cloudflare's own
// siteverify behavior; the Worker is a thin proxy.)
return jsonResponse(response, 200, env);
} catch (err) {
if (err instanceof SiteverifyError) {
const body = errorResponse(err.code, Date.now() - startedAt, WORKER_VERSION);
logSiteverify('error', {
durationMs: Date.now() - startedAt,
errorCodes: [err.code],
remoteipPresent: !!remoteip,
idempotencyKeyPresent: !!parsed.idempotency_key,
});
return jsonResponse(body, err.status, env);
}
const body = errorResponse('internal-error', Date.now() - startedAt, WORKER_VERSION);
logSiteverify('error', {
durationMs: Date.now() - startedAt,
errorCodes: ['internal-error'],
remoteipPresent: !!remoteip,
idempotencyKeyPresent: !!parsed.idempotency_key,
});
return jsonResponse(body, 500, env);
}
}
function handleHealth(env: Env): Response {
return jsonResponse(
{
ok: true,
version: WORKER_VERSION,
service: 'turnstile-siteverify',
docs: 'https://developers.cloudflare.com/turnstile/spin/',
},
200,
env,
);
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: corsHeaders(env) });
}
const url = new URL(request.url);
if (request.method === 'GET' && (url.pathname === '/' || url.pathname === '/health' || url.pathname === '/healthz')) {
return handleHealth(env);
}
// Post-deploy form (served from public/post-deploy.html via the ASSETS binding).
// The form prefills the Worker URL and lets a Deploy-button user create a widget
// without leaving the flow.
if (request.method === 'GET' && url.pathname === '/post-deploy') {
if (env.ASSETS) {
return env.ASSETS.fetch(new Request(new URL('/post-deploy.html', url.origin)));
}
}
const isSiteverifyPath = url.pathname === '/' || url.pathname === '/siteverify';
if (request.method === 'POST' && isSiteverifyPath) {
return handleSiteverify(request, env);
}
return jsonResponse(
{
success: false,
'error-codes': ['bad-request'],
hint: 'POST a Turnstile token to / (or /siteverify) as JSON or form-encoded body.',
},
404,
env,
);
},
} satisfies ExportedHandler<Env>;
@@ -0,0 +1,34 @@
/**
* Structured logging helpers. Output is JSON-per-line so Workers Observability
* + log-shipping pipelines can parse + index without regex hacks.
*/
import type { SiteverifyLogEntry, ErrorCode } from './types.js';
export interface LogContext {
durationMs: number;
upstreamStatus?: number;
errorCodes?: ErrorCode[];
cdataValue?: string;
remoteipPresent: boolean;
idempotencyKeyPresent: boolean;
}
export function logSiteverify(outcome: 'ok' | 'fail' | 'error', ctx: LogContext): void {
const entry: SiteverifyLogEntry = {
level: outcome === 'ok' ? 'info' : outcome === 'fail' ? 'warn' : 'error',
event: 'siteverify',
outcome,
duration_ms: ctx.durationMs,
upstream_status: ctx.upstreamStatus,
error_codes: ctx.errorCodes ?? [],
cdata_present: typeof ctx.cdataValue === 'string' && ctx.cdataValue.length > 0,
cdata_value: ctx.cdataValue,
remoteip_present: ctx.remoteipPresent,
idempotency_key_present: ctx.idempotencyKeyPresent,
ts: new Date().toISOString(),
};
// console.log goes to Workers logs and Observability.
// Using a JSON line for machine-readability.
console.log(JSON.stringify(entry));
}
@@ -0,0 +1,95 @@
/**
* Types for Turnstile siteverify request + response.
*
* Mirrors Cloudflare's documented siteverify API:
* https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
*/
export interface Env {
/** Turnstile secret key. Set via `wrangler secret put TURNSTILE_SECRET_KEY`. */
TURNSTILE_SECRET_KEY: string;
/** CORS allowed origin. Default "*" for dev; must be set explicitly for prod. */
ALLOWED_ORIGIN: string;
/**
* Optional. If set, the Worker rejects siteverify responses whose `hostname`
* does not match this value. Defends against cross-site token replay.
* Leave empty to accept any hostname (default; for multi-site deployments).
*/
EXPECTED_HOSTNAME?: string;
/** Log verbosity. One of: debug, info, warn, error. Defaults to info. */
LOG_LEVEL?: 'debug' | 'info' | 'warn' | 'error';
/** Static assets binding (post-deploy form). Set automatically by [assets] config. */
ASSETS?: Fetcher;
}
/**
* Input we accept from the customer's frontend.
* Both form-encoded and JSON bodies are supported.
*/
export interface SiteverifyRequest {
/** The Turnstile token from the widget. */
token: string;
/** Optional visitor IP. If omitted, we use CF-Connecting-IP. */
remoteip?: string;
/** Optional idempotency key for retries. */
idempotency_key?: string;
}
/**
* Cloudflare's siteverify response shape.
* See: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#accepted-parameters
*/
export interface SiteverifyResponse {
success: boolean;
challenge_ts?: string;
hostname?: string;
action?: string;
cdata?: string;
'error-codes': ErrorCode[];
/** Free-form metadata appended by this Worker. Not from upstream. */
_worker?: WorkerMetadata;
}
export interface WorkerMetadata {
/** Latency of the upstream siteverify call, milliseconds. */
duration_ms: number;
/** This Worker's version, from package.json. */
worker_version: string;
}
/**
* Documented error codes from Turnstile siteverify.
* See: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#error-codes
*/
export type ErrorCode =
| 'missing-input-secret'
| 'invalid-input-secret'
| 'missing-input-response'
| 'invalid-input-response'
| 'bad-request'
| 'timeout-or-duplicate'
| 'internal-error'
// Worker-side codes (not from upstream)
| 'missing-token'
| 'invalid-content-type'
| 'upstream-unreachable'
| 'upstream-timeout'
| 'hostname-mismatch';
/**
* Structured log emitted per siteverify call.
* These show up in Workers Observability.
*/
export interface SiteverifyLogEntry {
level: 'info' | 'warn' | 'error';
event: 'siteverify';
outcome: 'ok' | 'fail' | 'error';
duration_ms: number;
upstream_status?: number;
error_codes: ErrorCode[];
cdata_present: boolean;
cdata_value?: string;
remoteip_present: boolean;
idempotency_key_present: boolean;
ts: string;
}
@@ -0,0 +1,101 @@
/**
* Calls Cloudflare's siteverify endpoint. One retry on transient (5xx + network)
* failures, with a tight timeout. Returns the upstream JSON unmodified plus
* Worker metadata.
*/
import { SiteverifyError } from './errors.js';
import type { Env, SiteverifyRequest, SiteverifyResponse } from './types.js';
const SITEVERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
const UPSTREAM_TIMEOUT_MS = 5000;
const RETRY_ON_STATUSES = new Set([500, 502, 503, 504]);
export interface ValidateResult {
response: SiteverifyResponse;
upstreamStatus: number;
durationMs: number;
}
/**
* Validate a Turnstile token against Cloudflare's siteverify endpoint.
*
* @throws SiteverifyError on Worker-level failures (missing secret, unreachable upstream, timeout)
*/
export async function validate(req: SiteverifyRequest, env: Env, workerVersion: string): Promise<ValidateResult> {
if (!env.TURNSTILE_SECRET_KEY) {
throw new SiteverifyError(500, 'missing-input-secret', 'TURNSTILE_SECRET_KEY not configured');
}
if (!req.token) {
throw new SiteverifyError(400, 'missing-token', 'token is required');
}
const body = new FormData();
body.append('secret', env.TURNSTILE_SECRET_KEY);
body.append('response', req.token);
if (req.remoteip) body.append('remoteip', req.remoteip);
if (req.idempotency_key) body.append('idempotency_key', req.idempotency_key);
const start = Date.now();
let lastErr: unknown = null;
for (let attempt = 0; attempt < 2; attempt++) {
try {
const result = await fetchWithTimeout(SITEVERIFY_URL, body, UPSTREAM_TIMEOUT_MS);
const durationMs = Date.now() - start;
if (result.status >= 200 && result.status < 300) {
const data = (await result.json()) as SiteverifyResponse;
data._worker = {
duration_ms: durationMs,
worker_version: workerVersion,
};
// Hostname-mismatch defense. If the customer set EXPECTED_HOSTNAME,
// reject responses whose hostname doesn't match — defends against
// cross-site token replay even if a token leaks.
if (data.success && env.EXPECTED_HOSTNAME && data.hostname && data.hostname !== env.EXPECTED_HOSTNAME) {
const rejected: SiteverifyResponse = {
...data,
success: false,
'error-codes': ['hostname-mismatch'],
};
return { response: rejected, upstreamStatus: result.status, durationMs };
}
return { response: data, upstreamStatus: result.status, durationMs };
}
if (!RETRY_ON_STATUSES.has(result.status) || attempt === 1) {
// Don't retry on 4xx, return the upstream body as-is so customers
// see exactly what siteverify said.
const data = (await result.json().catch(() => ({}))) as Partial<SiteverifyResponse>;
const response: SiteverifyResponse = {
success: false,
'error-codes': data['error-codes'] ?? ['bad-request'],
...data,
_worker: { duration_ms: durationMs, worker_version: workerVersion },
};
return { response, upstreamStatus: result.status, durationMs };
}
// 5xx + first attempt: retry
} catch (err) {
lastErr = err;
if (attempt === 1) break;
}
}
if (lastErr instanceof DOMException && lastErr.name === 'AbortError') {
throw new SiteverifyError(504, 'upstream-timeout', `Upstream timeout after ${UPSTREAM_TIMEOUT_MS}ms`);
}
throw new SiteverifyError(502, 'upstream-unreachable', `Upstream siteverify unreachable: ${String(lastErr)}`);
}
async function fetchWithTimeout(url: string, body: FormData, timeoutMs: number): Promise<Response> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, {
method: 'POST',
body,
signal: controller.signal,
});
} finally {
clearTimeout(timer);
}
}