Skip to content

Add per-agent secrets, build signing, and forge framework#10

Merged
initializ-mk merged 1 commit into
mainfrom
core/secrets
Feb 28, 2026
Merged

Add per-agent secrets, build signing, and forge framework#10
initializ-mk merged 1 commit into
mainfrom
core/secrets

Conversation

@initializ-mk
Copy link
Copy Markdown
Contributor

Summary

  • Encrypted secrets management — New forge-core/secrets package with AES-256-GCM encryption (Argon2id key derivation). Per-agent secret files (<agent>/.forge/secrets.enc) with global fallback (~/.forge/secrets.enc). New forge secret set|get|list|delete [--local] commands. Runtime passphrase prompting via TTY, smart init passphrase (single prompt for existing file, double for first-time setup).
  • Build signing & verification — Ed25519 signing of build artifacts (checksums.json with SHA-256 checksums + signature). New forge key generate|trust|list commands. Runtime verification against trusted keyring. Secret safety build stage prevents secrets from leaking into container images.
  • Framework rename — Default framework renamed from custom to forge (backward compatible). Entrypoint is now optional for forge framework (uses built-in LLM executor). Dead agent.py/main.go scaffolding removed for forge framework. All tests updated across forge-core and forge-cli.

Test plan

  • gofmt -w forge-core/ forge-cli/ — no formatting issues
  • golangci-lint run ./forge-core/... ./forge-cli/... — 0 issues
  • go test ./... passes in both forge-core and forge-cli
  • forge init my-agent → secrets stored in my-agent/.forge/secrets.enc
  • forge run prompts for passphrase when FORGE_PASSPHRASE not set
  • forge secret set KEY val --local + forge secret get KEY round-trip
  • forge key generate + forge build produces signed checksums.json
  • forge init with existing ~/.forge/secrets.enc prompts once (no confirm)
  • Backward compat: framework: custom still accepted in forge.yaml

…mework to forge

Secrets:
- Add forge-core/secrets package with AES-256-GCM encrypted file provider,
  env provider, and chain provider (Argon2id key derivation)
- Add `forge secret set|get|list|delete` commands with --local flag for
  per-agent secrets (<agent-dir>/.forge/secrets.enc vs ~/.forge/secrets.enc)
- Add `forge key generate|trust|list` for Ed25519 signing key management
- Runtime passphrase prompting via TTY when FORGE_PASSPHRASE is not set
- Smart passphrase handling in forge init (single prompt for existing file,
  double prompt with confirmation for first-time setup)
- Per-agent secret provider chain: agent-local → global → env
- Add .forge/ to gitignore template, *.enc to dockerignore
- Secret safety build stage blocks prod builds leaking secrets into containers
- Build signing stage computes SHA-256 checksums and Ed25519 signatures
- Runtime verification of checksums.json against trusted keyring

Framework rename:
- Rename default framework from "custom" to "forge" (backward compatible)
- Make entrypoint optional for forge framework (uses built-in LLM executor)
- Remove dead agent.py/main.go scaffolding for forge framework
- Update plugin Name() to return "forge", normalize "custom" → "forge" in
  build pipeline
- Update all tests across forge-core and forge-cli

README:
- Add Secrets section (encrypted storage, per-agent, runtime prompting)
- Add Build Signing & Verification section
- Update configuration reference, command reference, and architecture tree
@initializ-mk initializ-mk merged commit 9d6f249 into main Feb 28, 2026
9 checks passed
naveen-kurra pushed a commit to naveen-kurra/forge that referenced this pull request May 23, 2026
…tializ#10)

Reviewer flagged: resolveAuth's comment claimed the loopback static_token
is "ALWAYS prepended at the chain head (regardless of source)", but the
actual code is conditional on r.authToken != "". It works in practice
only because ResolveAuth() always mints a token in the non-NoAuth path.
Any future refactor that short-circuits that path would silently break
channel-adapter callbacks — no compile error, no test failure, just
"my Slack bot stopped working after we updated forge" weeks later.

Fix is documentation + an invariant test. The conditional itself is
correct (--no-auth path returns early before the prepend, and that path
also doesn't mint a token), so the code stays as-is. What was missing
is making the upstream dependency explicit so a future maintainer
either preserves the invariant or notices when they don't.

Changes:
- resolveAuth doc: replaced "ALWAYS prepended" with an explicit
  description of the precondition (ResolveAuth() must have minted a
  token) and a reference to the pinning test. Inline comment at the
  prepend site explains why the conditional is defensive, not
  load-bearing.
- ResolveAuth() doc: states the invariant it maintains (after nil
  return, either r.authToken != "" or r.cfg.NoAuth is true) and names
  the downstream consumer + test.
- New tests:
    TestResolveAuth_InvariantMintsTokenInNonNoAuthPath
      Direct invariant pin: non-NoAuth + ResolveAuth() returns nil →
      r.authToken MUST be non-empty. Failure message tells future
      maintainers what they broke and where to look.
    TestResolveAuth_NoAuthPathDoesNotMintToken
      Counterpart: --no-auth path does NOT mint. Pins the "anonymous
      mode skips token generation" property too, so neither direction
      can drift.

If someone ever refactors ResolveAuth to skip minting in the non-NoAuth
path, the invariant test fails immediately — and the failure message
points straight at the loopback-prepend dependency.

Verification:
  go test -race ./forge-cli/runtime/ — green (incl. 2 new tests)
  full sweep — all packages pass
  golangci-lint v2.10.1 — 0 issues
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant