Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion agent-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@
},
"redact_secrets": {
"type": "boolean",
"description": "When true, the runtime auto-installs the redact_secrets builtin on all three of pre_tool_use (scrubs detected secrets from tool arguments), before_llm_call (scrubs the messages sent to the LLM), and tool_response_transform (scrubs tool output before it reaches event consumers, the persisted session, the post_tool_use hook input, or the next LLM call). The same hook entries can be authored directly in YAML for finer-grained control \u2014 see the hooks.tool_response_transform / hooks.before_llm_call / hooks.pre_tool_use sections. Detection uses the portcullis ruleset (GitHub PATs, AWS keys, Stripe / Slack / GitLab tokens, JWTs, private keys, etc.). Each detected span is replaced with the literal '[REDACTED]'."
"default": true,
"description": "Enabled by default. When true (the default), the runtime auto-installs the redact_secrets builtin on all three of pre_tool_use (scrubs detected secrets from tool arguments), before_llm_call (scrubs the messages sent to the LLM), and tool_response_transform (scrubs tool output before it reaches event consumers, the persisted session, the post_tool_use hook input, or the next LLM call). Set to false to opt out. The same hook entries can be authored directly in YAML for finer-grained control \u2014 see the hooks.tool_response_transform / hooks.before_llm_call / hooks.pre_tool_use sections. Detection uses the portcullis ruleset (GitHub PATs, AWS keys, Stripe / Slack / GitLab tokens, JWTs, private keys, etc.). Each detected span is replaced with the literal '[REDACTED]'."
},
"max_iterations": {
"type": "integer",
Expand Down
11 changes: 8 additions & 3 deletions examples/redact_secrets.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Demonstrates the redact_secrets feature.
#
# `redact_secrets: true` is a single agent-level switch that wires up
# all THREE defenses against accidentally leaking credentials, tokens,
# or private keys:
# `redact_secrets` is enabled by default for every agent. This
# example sets it explicitly for documentation purposes; you would
# normally omit the field entirely. To opt out, set
# `redact_secrets: false`.
#
# The single agent-level switch wires up all THREE defenses against
# accidentally leaking credentials, tokens, or private keys:
#
# 1. A pre_tool_use builtin hook that scrubs detected secrets from
# the arguments of every tool call, before the tool sees them.
Expand Down Expand Up @@ -37,6 +41,7 @@ agents:
instruction: |
You are a helpful assistant. If the user accidentally pastes a token,
do your best work without echoing the secret back.
# On by default; shown explicitly here. Set to `false` to opt out.
redact_secrets: true
toolsets:
- type: shell
11 changes: 5 additions & 6 deletions examples/redact_secrets_hooks.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Demonstrates wiring the redact_secrets builtin from YAML hooks
# directly, instead of through the agent-level `redact_secrets: true`
# shorthand.
# directly, instead of through the agent-level `redact_secrets` flag
# (which is enabled by default and auto-injects the same entries).
#
# `redact_secrets: true` is the one-liner equivalent of the entries
# below — it auto-injects the same three hook entries when the runtime
# constructs the agent. Spelling them out by hand is useful when you
# want to:
# Auto-injection is idempotent against manually-written entries that
# name the same builtin, so spelling them out by hand is safe and is
# useful when you want to:
#
# * scope the rewrite to a subset of tools (set `matcher:` to a
# regex instead of `*`),
Expand Down
66 changes: 66 additions & 0 deletions pkg/config/latest/redact_secrets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package latest

import (
"testing"

"github.com/goccy/go-yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRedactSecretsEnabledDefault(t *testing.T) {
t.Parallel()

tru, fls := true, false

tests := []struct {
name string
cfg *AgentConfig
want bool
}{
{name: "nil receiver defaults to on", cfg: nil, want: true},
{name: "field omitted defaults to on", cfg: &AgentConfig{}, want: true},
{name: "explicit true is on", cfg: &AgentConfig{RedactSecrets: &tru}, want: true},
{name: "explicit false opts out", cfg: &AgentConfig{RedactSecrets: &fls}, want: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tt.want, tt.cfg.RedactSecretsEnabled())
})
}
}

func TestRedactSecretsYAMLRoundTrip(t *testing.T) {
t.Parallel()

tests := []struct {
name string
yaml string
wantSet bool // whether the pointer should be non-nil
wantVal bool // value to expect when set
want bool // expected effective value via RedactSecretsEnabled
}{
{name: "omitted", yaml: "model: openai/gpt-5", wantSet: false, want: true},
{name: "explicit true", yaml: "model: openai/gpt-5\nredact_secrets: true", wantSet: true, wantVal: true, want: true},
{name: "explicit false", yaml: "model: openai/gpt-5\nredact_secrets: false", wantSet: true, wantVal: false, want: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

var cfg AgentConfig
require.NoError(t, yaml.Unmarshal([]byte(tt.yaml), &cfg))

if tt.wantSet {
require.NotNil(t, cfg.RedactSecrets, "field should be set")
assert.Equal(t, tt.wantVal, *cfg.RedactSecrets)
} else {
assert.Nil(t, cfg.RedactSecrets, "field should be nil when omitted")
}
assert.Equal(t, tt.want, cfg.RedactSecretsEnabled())
})
}
}
17 changes: 16 additions & 1 deletion pkg/config/latest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,11 @@ type AgentConfig struct {
// hook entries by hand — the runtime auto-injects them when this
// flag is true. See pkg/hooks/builtins/redact_secrets.go for the
// hook-side implementation.
RedactSecrets bool `json:"redact_secrets,omitempty"`
//
// Pointer (tri-state) so we can distinguish "unset" (nil → default
// on) from "explicitly disabled" (false). Use
// [AgentConfig.RedactSecretsEnabled] to read the effective value.
RedactSecrets *bool `json:"redact_secrets,omitempty"`
CodeModeTools bool `json:"code_mode_tools,omitempty"`
AddDescriptionParameter bool `json:"add_description_parameter,omitempty"`
MaxIterations int `json:"max_iterations,omitempty"`
Expand Down Expand Up @@ -600,6 +604,17 @@ func (a *AgentConfig) GetFallbackModels() []string {
return nil
}

// RedactSecretsEnabled reports the effective value of the agent's
// redact_secrets flag. The feature is on by default: a nil pointer
// (the field omitted from YAML) means enabled, an explicit
// `redact_secrets: false` is the only way to disable it.
func (a *AgentConfig) RedactSecretsEnabled() bool {
if a == nil || a.RedactSecrets == nil {
return true
}
return *a.RedactSecrets
}

// GetFallbackRetries returns the fallback retries from the config.
func (a *AgentConfig) GetFallbackRetries() int {
if a.Fallback != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/teamloader/teamloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func LoadWithConfig(ctx context.Context, agentSource config.Source, runConfig *c
agent.WithAddDate(agentConfig.AddDate),
agent.WithAddEnvironmentInfo(agentConfig.AddEnvironmentInfo),
agent.WithAddDescriptionParameter(agentConfig.AddDescriptionParameter),
agent.WithRedactSecrets(agentConfig.RedactSecrets),
agent.WithRedactSecrets(agentConfig.RedactSecretsEnabled()),
agent.WithAddPromptFiles(promptFiles),
agent.WithMaxIterations(agentConfig.MaxIterations),
agent.WithMaxConsecutiveToolCalls(agentConfig.MaxConsecutiveToolCalls),
Expand Down
Loading