Skip to content

v0.10.0

Choose a tag to compare

@initializ-mk initializ-mk released this 18 May 17:35
16c2279

Forge v0.10.0 — Dynamic Secret Overlay, Configurable Security Scoring, K8s Channel Manifests, Slack Bot Admission

Release Date: May 18, 2026
Full Changelog: v0.9.2...v0.10.0

Forge v0.10.0 makes encrypted secrets, security policy, channel deployment, and inter-bot communication first-class operator concerns. Skill-declared env vars now flow from the encrypted store to the agent process without code changes; security audits can be downgraded via YAML policy with full auditability; Kubernetes manifests finally include channel env vars from the same source as docker-compose; and Slack agents can now accept @-mentions from operator-allowlisted bots while staying immune to self-loops. Two latent bugs in the secret provider chain are also fixed. 62 files changed, 2,718 insertions, 728 deletions across forge-cli, forge-core, forge-plugins, and forge-skills.


Highlights

# Area Change Issue / PR
1 Secrets Skill-declared env vars in encrypted store reach the agent's OS env via dynamic provider.List() discovery — no hardcoded key list #48 / #51
2 Skills audit New forge skills audit --policy <path> overrides scoring and policy checks; downgrades carry (via policy) / (acknowledged by policy) annotations #49 / #53
3 K8s + channels Channel env vars from <channel>-config.yaml now flow into k8s/secrets.yaml, k8s/deployment.yaml, and docker-compose.yaml from a single canonical source #50 / #54
4 Secrets Stale global ~/.forge/secrets.enc with a different passphrase no longer poisons the chain — eager-load validation drops it with a clear warning #52 / #57
5 Slack New allow_bot_ids setting admits @-mentions from operator-trusted bots; the agent's own bot_id is unconditionally dropped (self-loop guard) #55 / #56

What's New

Dynamic Secret Overlay for Skill-Declared Env Vars (PR #51, closes #48)

Before: the runtime overlaid only a hardcoded set of LLM/channel keys (OPENAI_API_KEY, SLACK_BOT_TOKEN, etc.) from the encrypted secrets store. A skill declaring metadata.forge.requires.env.required: [ACME_API_TOKEN] would see an empty value at runtime, even after forge secret set ACME_API_TOKEN ....

Now: a new secretOverlayKeys(provider) helper unions the builtin set with every key the provider exposes via Provider.List(). Custom skill-declared keys flow through the encrypted store to the OS env automatically — no code change required when adding a new skill.

# skills/my-skill/SKILL.md
metadata:
  forge:
    requires:
      env:
        required: [ACME_API_TOKEN]
forge secret --local set ACME_API_TOKEN my-secret-value
forge run   # ACME_API_TOKEN is now in the agent's environment

Cross-category secret-reuse detection continues to operate on the builtin set only — custom keys have no defined purpose category and are not part of the reuse check.

Policy-Configurable Security Scoring (PR #53, closes #49)

forge skills audit now accepts a --policy <path> flag that loads a YAML SecurityPolicy. The policy affects both policy-violation checks (existing) and scoring (new). Three new scoring overrides let operators down-weight builtin classifications:

# policy.yaml
script_policy: allow
max_risk_score: 90

trusted_domains:    [internal.example.com]   # +2 instead of +10 (unknown)
acknowledged_bins:  [python]                 # +3 instead of +15 (builtin high-risk)
acknowledged_env:   [DB_PASSWORD]            # +5 instead of +10 (builtin sensitive)
forge skills audit --dir skills --policy policy.yaml

Every override is auditable. Affected factors carry (via policy) or (acknowledged by policy) in their description, visible in both --format text and --format json:

egress   +2   trusted domain (via policy): internal.example.com
binary   +3   high-risk binary (acknowledged by policy): python
env      +5   sensitive variable (acknowledged by policy): DB_PASSWORD

Scoring overrides can only down-weight builtin classifications — a binary not in the high-risk set stays at +3 even if listed in acknowledged_bins. This prevents accidental score inflation.

API change: AnalyzeSkillDescriptor and AnalyzeSkillEntry now take a SecurityPolicy parameter. Pass SecurityPolicy{} to reproduce historical default scoring exactly.

Channel Env Vars in Kubernetes Manifests (PR #54, closes #50)

Before this release, forge build generated k8s/deployment.yaml and k8s/secrets.yaml containing only skill-declared env vars. Channel env vars (SLACK_BOT_TOKEN, TELEGRAM_BOT_TOKEN, etc.) were silently dropped — even though forge package --with-channels injected them into docker-compose via a hardcoded switch statement. The two output paths disagreed from the same forge.yaml.

A new ChannelsStage (forge-cli/build/channels_stage.go) reads each <channel>-config.yaml and unions every _env-suffixed setting value into Spec.Requirements.EnvRequired. EnvVarsFromConfig in forge-cli/channels/env.go is the canonical helper; generateDockerCompose in cmd/package.go calls the same function, so both output paths produce a consistent env-var set.

# slack-config.yaml — the canonical source
adapter: slack
settings:
  app_token_env: SLACK_APP_TOKEN
  bot_token_env: SLACK_BOT_TOKEN
  custom_env: MY_PROJECT_SLACK_OVERRIDE   # ← appears in K8s + docker-compose

After forge build:

# .forge-output/k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-agent-secrets
stringData:
  SLACK_APP_TOKEN: ""
  SLACK_BOT_TOKEN: ""
  MY_PROJECT_SLACK_OVERRIDE: ""
  TELEGRAM_BOT_TOKEN: ""

Adding a new channel adapter requires zero build-pipeline edits. Operators (and channel-adapter authors) just declare env vars via the _env suffix convention in their channel YAML.

Behavior change worth calling out: SLACK_SIGNING_SECRET is no longer injected. The Slack adapter is Socket Mode and never reads it; the previous hardcoded map included it erroneously. Operators using a custom Slack adapter that does need it can add signing_secret_env: SLACK_SIGNING_SECRET to their slack-config.yaml.

Slack Bot @-Mention Admission with Self-Loop Guard (PR #56, closes #55)

The Slack adapter previously dropped every event whose bot_id was non-empty — silently ignoring @-mentions from scheduler bots, monitoring tools, workflow steps, and CI bots even when an operator explicitly wired them up.

A new optional allow_bot_ids setting admits specific bots:

# slack-config.yaml
adapter: slack
settings:
  app_token_env: SLACK_APP_TOKEN
  bot_token_env: SLACK_BOT_TOKEN
  allow_bot_ids: B0123ABC,B0456DEF   # comma-separated bot_ids

Three coordinated rules keep loops bounded:

Rule Behavior
Self-loop guard (always on) The agent's own bot_id is dropped before the allowlist is consulted. No opt-out — even if its own ID is misconfigured into allow_bot_ids, the guard short-circuits.
Allowlist gate (opt-in) Bots not in allow_bot_ids stay dropped. Empty/absent setting preserves today's behavior (no other bots admitted).
Mention requirement (unchanged) Admitted bots still must include <@FORGE_AGENT> in the message text. Chatter without a tag is ignored.

resolveBotID now captures both user_id and bot_id from Slack's auth.test response — user_id drives mention matching; bot_id powers the self-loop guard. Drop reasons are emitted to stderr with the bot_id and a pointer to slack-config.yaml allow_bot_ids so operators can self-diagnose.

Stale Global Secrets File No Longer Poisons the Chain (PR #57, closes #52)

A latent bug in OverlaySecretsToEnv and Runner.buildSecretProvider — exposed during the validation of #51 — caused a stale ~/.forge/secrets.enc from an unrelated project (encrypted with a different passphrase) to break ChainProvider.List() for everyone. Local file's keys would silently disappear from the agent's env.

A new viableEncryptedFileProviders helper eagerly validates each candidate file at chain-build time:

Candidate state Behavior
File missing (os.IsNotExist) Silently skipped — common case, no log noise
Decryption succeeds Admitted; cleartext is cached for subsequent reads (no extra Argon2id)
Decryption fails (wrong passphrase, corruption) Dropped from the chain with a clear warning naming the file path
forge: skipping secrets provider that failed to load
  (label=global, path=/Users/.../.forge/secrets.enc,
   error=decrypting secrets file: ... wrong passphrase?)

ChainProvider semantics stay strict — non-NotFound errors from Get / List still propagate. The fix is at the chain-construction site: we don't admit providers we already know will fail.


Documentation

Each PR shipped with synced docs. Notable additions:


Upgrade Notes

For most users this is a drop-in upgrade — no forge.yaml changes needed.

  • forge-skills/analyzer API: AnalyzeSkillDescriptor and AnalyzeSkillEntry now take a third SecurityPolicy parameter. Pass SecurityPolicy{} for the historical default. Affects only direct importers of the analyzer package.
  • Slack docker-compose: SLACK_SIGNING_SECRET is no longer auto-injected. If you depended on it (you likely didn't — Socket Mode never reads it), add signing_secret_env: SLACK_SIGNING_SECRET to your slack-config.yaml.
  • Skill audit CLI: forge skills audit --dir <path> is required for subdirectory-style skills (skills/<name>/SKILL.md). The default still targets a flat SKILL.md.

Install

# Homebrew
brew upgrade initializ/tap/forge

# One-line install/upgrade
curl -sSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash

# Docker
docker pull ghcr.io/initializ/forge:v0.10.0
docker pull ghcr.io/initializ/forge:latest

Contributors

Thanks to the operators who reported the channel env / Slack bot / encrypted secrets issues that drove this release.


Reference