Redact direct API keys in request formatting
This commit is contained in:
@@ -97,7 +97,9 @@ _ = result.Artifact
|
|||||||
|
|
||||||
`RunResult` includes the run ID, output artifact, raw output, validation result, prompt/profile/model metadata, effective model parameters, input hashes, token/cache usage, and timing fields. Validation content failures return a successful `RunResult` with failed validation status. Runtime validation errors return `ErrValidation`.
|
`RunResult` includes the run ID, output artifact, raw output, validation result, prompt/profile/model metadata, effective model parameters, input hashes, token/cache usage, and timing fields. Validation content failures return a successful `RunResult` with failed validation status. Runtime validation errors return `ErrValidation`.
|
||||||
|
|
||||||
For the public Go API, pass provider credentials with `RunRequest.APIKey`. The value is request-scoped, uses `json:"-"`, is preferred over profile `api_key_env` by the default OpenAI-compatible client, and is not included in `PreparedRun` or `RunResult` JSON. Do not store raw keys in config, prompt files, or profile YAML.
|
For the public Go API, pass provider credentials with `RunRequest.APIKey`. The value is request-scoped, uses `json:"-"`, is preferred over profile `api_key_env` by the default OpenAI-compatible client, and is not included in `PreparedRun` or `RunResult` JSON. Normal Go string formatting of `RunRequest` reports only whether a direct key is set. Do not store raw keys in config, prompt files, or profile YAML.
|
||||||
|
|
||||||
|
Avoid logging raw request structs with reflection-based debug dumpers; exported fields remain visible to tools that bypass `String` and `GoString` methods.
|
||||||
|
|
||||||
## Inject An LLM Client
|
## Inject An LLM Client
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@ func (fakeLLM) Generate(ctx context.Context, req scriptorium.GenerateRequest) (*
|
|||||||
engine, err := scriptorium.NewEngine(cfg, scriptorium.WithLLMClient(fakeLLM{}))
|
engine, err := scriptorium.NewEngine(cfg, scriptorium.WithLLMClient(fakeLLM{}))
|
||||||
```
|
```
|
||||||
|
|
||||||
The injected client receives the rendered prompt, effective execution target, target presence metadata for explicit numeric overrides, structured-output spec, and request API key when provided. `GenerateRequest.APIKey` also uses `json:"-"`; custom and fake clients should avoid logging or serializing it. `WithLLMClient(nil)` returns `ErrInvalidConfig`.
|
The injected client receives the rendered prompt, effective execution target, target presence metadata for explicit numeric overrides, structured-output spec, and request API key when provided. `GenerateRequest.APIKey` also uses `json:"-"`, and normal Go string formatting reports only whether a direct key is set. Custom and fake clients should avoid logging or serializing it. `WithLLMClient(nil)` returns `ErrInvalidConfig`.
|
||||||
|
|
||||||
## Request Overrides
|
## Request Overrides
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -118,6 +119,79 @@ func TestPreparedRunJSONDoesNotExposeSecretOrTargetPresence(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunRequestFormattingRedactsDirectAPIKey(t *testing.T) {
|
||||||
|
const secret = "run-request-secret"
|
||||||
|
req := scriptorium.RunRequest{
|
||||||
|
PromptID: "generic.markdown_summary",
|
||||||
|
ProfileID: "local-fast",
|
||||||
|
APIKey: secret,
|
||||||
|
Inputs: map[string]scriptorium.ArtifactRef{
|
||||||
|
"transcript": scriptorium.Inline("Rin opens the gate."),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, formatted := range []string{
|
||||||
|
fmt.Sprint(req),
|
||||||
|
fmt.Sprintf("%+v", req),
|
||||||
|
fmt.Sprintf("%#v", req),
|
||||||
|
} {
|
||||||
|
if strings.Contains(formatted, secret) {
|
||||||
|
t.Fatalf("formatted RunRequest leaked API key: %s", formatted)
|
||||||
|
}
|
||||||
|
if !strings.Contains(formatted, "APIKeySet:true") {
|
||||||
|
t.Fatalf("formatted RunRequest should indicate an API key is set, got %s", formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected RunRequest to marshal, got %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(payload), secret) {
|
||||||
|
t.Fatalf("RunRequest JSON leaked API key: %s", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateRequestFormattingRedactsDirectAPIKey(t *testing.T) {
|
||||||
|
const secret = "generate-request-secret"
|
||||||
|
req := scriptorium.GenerateRequest{
|
||||||
|
Prompt: scriptorium.RenderedPrompt{Messages: []scriptorium.RenderedMessage{
|
||||||
|
{Role: "user", Content: "secret prompt content"},
|
||||||
|
}},
|
||||||
|
Target: scriptorium.ExecutionTarget{
|
||||||
|
Model: "test-model",
|
||||||
|
ExtraParams: map[string]any{
|
||||||
|
"provider_option": "on",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
APIKey: secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, formatted := range []string{
|
||||||
|
fmt.Sprint(req),
|
||||||
|
fmt.Sprintf("%+v", req),
|
||||||
|
fmt.Sprintf("%#v", req),
|
||||||
|
} {
|
||||||
|
if strings.Contains(formatted, secret) {
|
||||||
|
t.Fatalf("formatted GenerateRequest leaked API key: %s", formatted)
|
||||||
|
}
|
||||||
|
if strings.Contains(formatted, "secret prompt content") {
|
||||||
|
t.Fatalf("formatted GenerateRequest leaked prompt content: %s", formatted)
|
||||||
|
}
|
||||||
|
if !strings.Contains(formatted, "APIKeySet:true") {
|
||||||
|
t.Fatalf("formatted GenerateRequest should indicate an API key is set, got %s", formatted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected GenerateRequest to marshal, got %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(payload), secret) {
|
||||||
|
t.Fatalf("GenerateRequest JSON leaked API key: %s", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPreparePreservesExplicitZeroExecutionOverrides(t *testing.T) {
|
func TestPreparePreservesExplicitZeroExecutionOverrides(t *testing.T) {
|
||||||
engine := newExampleEngine(t)
|
engine := newExampleEngine(t)
|
||||||
zeroFloat := 0.0
|
zeroFloat := 0.0
|
||||||
|
|||||||
51
formatting.go
Normal file
51
formatting.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package scriptorium
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// String returns a concise request summary without exposing direct API keys.
|
||||||
|
func (r RunRequest) String() string {
|
||||||
|
return r.redactedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString returns a concise request summary without exposing direct API keys.
|
||||||
|
func (r RunRequest) GoString() string {
|
||||||
|
return r.redactedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RunRequest) redactedString() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"scriptorium.RunRequest{PromptID:%q PromptVersion:%q ProfileID:%q APIKeySet:%t Inputs:%d Vars:%d ExecutionSet:%t ValidationSet:%t Metadata:%d}",
|
||||||
|
r.PromptID,
|
||||||
|
r.PromptVersion,
|
||||||
|
r.ProfileID,
|
||||||
|
r.APIKey != "",
|
||||||
|
len(r.Inputs),
|
||||||
|
len(r.Vars),
|
||||||
|
r.Execution != nil,
|
||||||
|
r.Validation != nil,
|
||||||
|
len(r.Metadata),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a concise request summary without exposing direct API keys or
|
||||||
|
// rendered prompt content.
|
||||||
|
func (r GenerateRequest) String() string {
|
||||||
|
return r.redactedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString returns a concise request summary without exposing direct API keys or
|
||||||
|
// rendered prompt content.
|
||||||
|
func (r GenerateRequest) GoString() string {
|
||||||
|
return r.redactedString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r GenerateRequest) redactedString() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"scriptorium.GenerateRequest{Messages:%d Model:%q APIKeySet:%t StructuredOutputSet:%t ExtraParams:%d}",
|
||||||
|
len(r.Prompt.Messages),
|
||||||
|
r.Target.Model,
|
||||||
|
r.APIKey != "",
|
||||||
|
r.StructuredOutput != nil,
|
||||||
|
len(r.Target.ExtraParams),
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user