v0.24.0
Highlights
GraphQL repository scoping — GitHub App installation tokens can perform GraphQL mutations on any public repository, creating an exfiltration channel where a sandboxed agent could post secrets to attacker-controlled issues. The proxy now resolves configured repository node IDs at token refresh time and rejects any GraphQL mutation targeting an out-of-scope repository. A second defense layer decodes GitHub node IDs in all *Id input fields to verify repository ownership, catching mutations like addComment(subjectId: ...) that don't use repositoryId directly. Unrecognized node ID formats are blocked by default (fail-secure). (#533, #545, #565)
GraphQL operation allowlist — A new default-deny operation filter for GraphQL endpoints at the proxy layer. The network-allowlist.yaml graphql block lets you whitelist queries, mutations, and subscriptions by top-level field name using fnmatch patterns. This closes the gap where mutations using non-repositoryId targeting fields could bypass repository scope checking entirely — Layer 1 blocks the operation before Layer 2 (repository scoping) even runs. (#544, #548, #552)
Security Fixes
This release addresses findings from a comprehensive security review of the network proxy:
- AWS secret key exposure — The real AWS secret access key was passed to the container environment instead of a surrogate, contrary to spec. The proxy re-signs from scratch and never needs the container's copy. Now all three credential components are surrogated. (#556)
- Path traversal bypass — URL paths containing
/../segments (including percent-encoded variants) could bypass URL-prefix allowlist restrictions. The proxy now rejects path traversal sequences after decoding. (#557) - Duplicate header bypass — Duplicate HTTP headers (e.g., two
Authorizationentries) could evade token replacement: only the first value was inspected, letting an attacker's duplicate survive. A new collapse step keeps only the replaced entry across all credential paths. (#559, #566) - Race condition in flow matching — A shared mutable variable for the matched URL entry meant concurrent proxy flows could overwrite each other's match. Replaced with per-flow metadata. (#555)
- GraphQL scope bypass via encoded path — Percent-encoding characters in the
/graphqlpath (e.g.,/%67raphql) skipped scope validation while still getting the real token injected. (#543) - GraphQL scope bypass via list variables — List-of-dict GraphQL variables (e.g.,
[{"repositoryId": "R_evil"}]) were not recursed into, allowing scope restrictions to be bypassed. (#562) - Case-insensitive host matching — Domain matching in the allowlist was case-sensitive, contrary to RFC 4343. A crafted
Host: API.GITHUB.COMcould bypass allowlist rules. (#560) - GraphQL body size limit — The repository scope checker lacked the 1 MiB body size limit that the operation checker enforced, allowing CPU exhaustion via oversized GraphQL payloads. (#558)
- DNS compression pointer injection — The DNS parser accepted compression pointers (label bytes >= 0xC0), which could cause reads beyond the question section on crafted packets. (#564)
- AWS signing encoding fix — Multi-byte UTF-8 characters in S3 paths produced invalid percent-encoding, causing signature mismatches (fail-closed, not a security bypass). (#561)
- Presigned URL re-signing — The canonical request used the surrogate credential instead of the real one, producing invalid signatures (fail-closed). (#563)
Other Changes
- Added
enableproperty to scheduled tasks, allowing tasks to be disabled without removing them from config. (#542) - Centralized proxy logging to one
ALLOWED/BLOCKEDline per request, eliminating duplicates and contradictory log entries. (#541) - Documented that only GitHub App credentials protect against public repo exfiltration via GraphQL; classic PATs provide no protection. (#538)
- Updated
.airut/README.mdwith GraphQL filtering, wildcard hosts, and credential scope documentation. (#554) - Added sandbox escape pentest workflow for repeatable penetration testing of sandbox isolation. (#529)
Upgrade
airut update
If airut check reports a pending config schema migration after updating, run airut migrate.