-
Notifications
You must be signed in to change notification settings - Fork 540
Bump default gh-aw-firewall to v0.27.7 and refresh generated artifacts #40208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -993,6 +993,11 @@ func TestValidateAWFConfigJSON_AllowsTemplatableModelFallbackEnabled(t *testing. | |
| require.NoError(t, err, "modelFallback.enabled expressions should pass compile-time schema validation") | ||
| } | ||
|
|
||
| func TestValidateAWFConfigJSON_AllowsMaxCacheMisses(t *testing.T) { | ||
| err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"maxCacheMisses":3}}`) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test covers only one happy-path for 💡 Suggested additionsThe schema declares func TestValidateAWFConfigJSON_RejectsMaxCacheMissesZero(t *testing.T) {
err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"maxCacheMisses":0}}`)
require.Error(t, err, "maxCacheMisses of 0 should fail schema validation (minimum: 1)")
}This PR also adds |
||
| require.NoError(t, err, "maxCacheMisses should pass compile-time schema validation") | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] Happy path only — the Adding a test that confirms 💡 Suggested testfunc TestValidateAWFConfigJSON_RejectsMaxCacheMissesZero(t *testing.T) {
err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"maxCacheMisses":0}}`)
require.Error(t, err, "maxCacheMisses: 0 should fail schema validation (minimum: 1)")
}This follows the pattern used by other schema-constraint tests in this file.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] Three new schema fields ( A future schema refresh could drop or change these fields and tests would not catch it. Given the existing pattern in this file, adding at least acceptance tests for each field would be low-effort. 💡 Suggested additionsfunc TestValidateAWFConfigJSON_AllowsAllowedModels(t *testing.T) {
err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"allowedModels":["*sonnet*"]}}`)
require.NoError(t, err, "allowedModels should pass compile-time schema validation")
}
func TestValidateAWFConfigJSON_AllowsDisallowedModels(t *testing.T) {
err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"disallowedModels":["*opus*"]}}`)
require.NoError(t, err, "disallowedModels should pass compile-time schema validation")
}
func TestValidateAWFConfigJSON_AllowsDiagnosticsCaptureBlockedRequests(t *testing.T) {
err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"diagnostics":{"captureBlockedRequests":"summary"}}}`)
require.NoError(t, err, "diagnostics.captureBlockedRequests should pass compile-time schema validation")
} |
||
|
|
||
| // TestBuildAWFConfigJSON_ValidateFlag verifies that schema validation runs when | ||
| // WorkflowData.ValidateAWFConfig is true (--validate mode) and is skipped otherwise. | ||
| func TestBuildAWFConfigJSON_ValidateFlag(t *testing.T) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://github.com/github/gh-aw-firewall/releases/download/v0.27.0/awf-config.schema.json", | ||
| "$id": "https://raw.githubusercontent.com/github/gh-aw-firewall/main/docs/awf-config.schema.json", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/zoom-out] The schema Previously it was 💡 SuggestionConsider using the v0.27.7 release URL instead: "$id": "https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json"If upstream deliberately changed to a floating
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Schema 💡 Details and suggested fix
schemaURL := fmt.Sprintf("https://github.com/github/gh-aw-firewall/releases/download/%s/awf-config.schema.json", constants.DefaultFirewallVersion)
loader.AddResource(schemaURL, schemaDoc)Generated AWF configs will therefore emit Consequences:
Suggested fix: Keep the "$id": "https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json"Or update |
||
| "title": "AWF Configuration", | ||
| "description": "JSON/YAML configuration for awf CLI. CLI flags override config file values. See https://github.com/github/gh-aw-firewall for documentation.", | ||
| "type": "object", | ||
|
|
@@ -53,7 +53,7 @@ | |
| }, | ||
| "enableTokenSteering": { | ||
| "type": "boolean", | ||
| "description": "Enable AI Credits budget steering. When true, the proxy injects budget-warning system messages at 80%, 90%, 95%, and 99% usage to nudge the agent to wrap up. Requires maxAiCredits. Default: false." | ||
| "description": "Enable effective token budget steering. When true, the proxy injects budget-warning system messages at 80%, 90%, 95%, and 99% usage to nudge the agent to wrap up. Requires maxEffectiveTokens. Default: false." | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
💡 DetailsThe description changed from:
To:
Any user who upgraded to v0.27.7 with a config like: { "apiProxy": { "enableTokenSteering": true, "maxAiCredits": 500 } }will find that token steering silently stops working (or behaves differently) because the upstream firewall now keys off This needs an explicit migration note in the CHANGELOG (or at minimum in the unreleased section) along the lines of:
|
||
| }, | ||
| "anthropicAutoCache": { | ||
| "type": "boolean", | ||
|
|
@@ -67,12 +67,12 @@ | |
| "maxEffectiveTokens": { | ||
| "type": "integer", | ||
| "minimum": 1, | ||
| "description": "Maximum cumulative effective tokens allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'effective_tokens_limit_exceeded'. Tokens are weighted: input ×1, cache-read ×0.1, output ×4, reasoning ×4. See spec §10." | ||
| "description": "Maximum cumulative effective tokens allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 403 and error type 'effective_tokens_limit_exceeded'. Tokens are weighted: input ×1, cache-read ×0.1, output ×4, reasoning ×4. See spec §10." | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Breaking behavioral change: limit-exceeded responses moved from HTTP 429 (retriable) to HTTP 403 (non-retriable) — not flagged as breaking in the CHANGELOG. 💡 DetailsThree guards changed their documented error status code from 429 and 403 carry fundamentally different retry semantics:
Real-world impact:
The CHANGELOG entry mentions "HTTP 403 behavior" but does not call this out as a breaking change for consumers that differentiate these status codes. The unreleased section should include an explicit migration note for anyone with retry or monitoring logic that keys on HTTP 429 for AWF-proxied requests. |
||
| }, | ||
| "maxAiCredits": { | ||
| "type": "number", | ||
| "exclusiveMinimum": 0, | ||
| "description": "Maximum cumulative AI credits allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'ai_credits_limit_exceeded'. AWF also enforces a non-overridable hard cap of 10,000 AI credits; values above 10,000 are effectively clamped." | ||
| "description": "Maximum cumulative AI credits allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 403 and error type 'ai_credits_limit_exceeded'. AWF also enforces a non-overridable hard cap of 10,000 AI credits; values above 10,000 are effectively clamped." | ||
| }, | ||
| "defaultAiCreditsPricing": { | ||
| "type": "object", | ||
|
|
@@ -104,7 +104,7 @@ | |
| }, | ||
| "modelMultipliers": { | ||
| "type": "object", | ||
| "description": "Per-model cost multipliers. Each model's weighted tokens are multiplied by this value before accumulation. Unlisted models use defaultModelMultiplier when set, otherwise the highest configured multiplier. See spec §10.2.", | ||
| "description": "Per-model multipliers for effective token accounting. Each model's weighted tokens are multiplied by this value before accumulation. Unlisted models use defaultModelMultiplier when set, otherwise the highest configured multiplier. See spec §10.2.", | ||
| "additionalProperties": { | ||
| "type": "number", | ||
| "exclusiveMinimum": 0 | ||
|
|
@@ -123,7 +123,7 @@ | |
| "maxTurns": { | ||
| "type": "integer", | ||
| "minimum": 1, | ||
| "description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'max_runs_exceeded'. See spec §11." | ||
| "description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 403 and error type 'max_runs_exceeded'. See spec §11." | ||
| }, | ||
| "maxRuns": { | ||
| "type": "integer", | ||
|
|
@@ -135,6 +135,11 @@ | |
| "minimum": 1, | ||
| "description": "Maximum number of upstream permission-denied (401/403) responses allowed per run. When reached, the API proxy rejects all subsequent requests with HTTP 403 and error type 'permission_denied_limit_exceeded', stopping the run to avoid wasting tokens on misconfigured or missing API credentials. When unset, the guard is disabled." | ||
| }, | ||
| "maxCacheMisses": { | ||
| "type": "integer", | ||
| "minimum": 1, | ||
| "description": "Maximum number of consecutive cache misses allowed per run. A miss is counted only for successful responses with non-zero input_tokens and zero cache_read_tokens. Responses with cache_read_tokens > 0 reset the streak. When reached, the API proxy rejects subsequent requests with HTTP 403 and error type 'max_cache_misses_exceeded'." | ||
| }, | ||
| "requestedModel": { | ||
| "type": "string", | ||
| "description": "Expected model name for pre-startup validation. When set, the API proxy validates at startup that this model is available in at least one provider's model catalogue. Emits a clear diagnostic if the model is retired, restricted, or misspelled. Does not block startup." | ||
|
|
@@ -214,6 +219,20 @@ | |
| } | ||
| } | ||
| }, | ||
| "allowedModels": { | ||
| "type": "array", | ||
| "description": "Allowlist of permitted model names (glob patterns). When set, only models matching at least one pattern are permitted. Uses case-insensitive glob matching with * as a wildcard. Evaluated after disallowedModels — a model in the denylist is always rejected even if it matches the allowlist. Examples: [\"*sonnet*\", \"*haiku*\"].", | ||
| "items": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "disallowedModels": { | ||
| "type": "array", | ||
| "description": "Denylist of prohibited model names (glob patterns). Models matching any pattern are rejected with HTTP 403 regardless of the allowedModels allowlist. Uses case-insensitive glob matching with * as a wildcard. Examples: [\"*opus*\", \"gpt-5*\"].", | ||
| "items": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "auth": { | ||
| "type": "object", | ||
| "description": "Authentication configuration for the API proxy sidecar. Enables OIDC-based credential exchange (e.g., GitHub OIDC → Azure AD, AWS STS, GCP Workload Identity, or Anthropic Workload Identity Federation). See docs/awf-config-spec.md §9.5.", | ||
|
|
@@ -360,6 +379,32 @@ | |
| "default": "/var/log/api-proxy" | ||
| } | ||
| } | ||
| }, | ||
| "diagnostics": { | ||
| "type": "object", | ||
| "description": "Opt-in diagnostics for blocked LLM requests. Writes body-shape records to blocked-request-diag.jsonl when a guard hard-rails a request.", | ||
| "additionalProperties": false, | ||
| "properties": { | ||
| "captureBlockedRequests": { | ||
| "description": "Enable capture of body-shape diagnostics for guard-blocked requests. 'summary' captures counts/sizes/hashes only (no content). 'redacted' adds first 200 chars per message. 'full' captures the entire body up to maxCapturedBytes. Maps to AWF_CAPTURE_BLOCKED_LLM_REQUESTS.", | ||
| "oneOf": [ | ||
| { | ||
| "type": "boolean" | ||
| }, | ||
| { | ||
| "type": "string", | ||
| "enum": ["summary", "redacted", "full"] | ||
| } | ||
| ], | ||
| "default": false | ||
| }, | ||
| "maxCapturedBytes": { | ||
| "type": "integer", | ||
| "description": "Maximum body bytes to include in a single 'full'-mode blocked-request-diag record. Maps to AWF_MAX_BLOCKED_CAPTURE_BYTES.", | ||
| "minimum": 1, | ||
| "default": 250000 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
|
|
@@ -634,6 +679,18 @@ | |
| "description": "Maximum number of bytes transferred per minute." | ||
| } | ||
| } | ||
| }, | ||
| "platform": { | ||
| "type": "object", | ||
| "description": "GitHub platform deployment type. Explicitly declares the environment so AWF can apply correct auth behavior (e.g. token vs Bearer prefix) without relying on heuristic detection.", | ||
| "additionalProperties": false, | ||
| "properties": { | ||
| "type": { | ||
| "type": "string", | ||
| "enum": ["github.com", "ghes", "ghec", "ghec-self-hosted"], | ||
| "description": "The GitHub deployment type. 'github.com' = GitHub.com (default), 'ghes' = GitHub Enterprise Server (on-premises), 'ghec' = GitHub Enterprise Cloud (*.ghe.com tenants), 'ghec-self-hosted' = GHEC with self-hosted runners." | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "$defs": { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/zoom-out] Container SHA pins appear to be missing from all regenerated lock files — this reduces supply chain security and reproducibility.
The comment above this constant says two recompile passes are needed (
make build && make recompile && make recompile) with the second pass resolving per-component SHA digests. The generated lock files only carry0.27.7tags without@sha256:pins (previous v0.27.6 lock files hadagent=sha256:5b778c...etc. embedded inimageTag). If v0.27.7 digests are not yet resolvable by the toolchain, please note that in the PR description so reviewers know this is intentional rather than a missed step.💡 What the diff shows
Before (v0.27.6):
After (v0.27.7):
Docker tags are mutable; without SHA pins, a compromised or accidentally overwritten tag could silently run different code.