Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
22 changes: 22 additions & 0 deletions .github/aw/syntax-agentic.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,28 @@ description: Agentic workflow specific frontmatter fields for GitHub Agentic Wor
- **Strict mode**: `sandbox.agent` blocks without an explicit `id: awf` are rejected in strict mode. Any non-nil, non-disabled agent config without `id`/`type` defaults to AWF at runtime.

- **`tools:`** - Tool configuration for the coding agent (`github`, `agentic-workflows`, `edit`, `web-fetch`, `web-search`, `bash`, `playwright`, custom MCP server names, plus `timeout`/`startup-timeout`/`cli-proxy`). See [syntax-tools-imports.md](syntax-tools-imports.md#tool-configuration) for the full schema (GitHub `mode`/`toolsets`/integrity fields, bash allowlist decision rule, Playwright CLI mode).
- **`tools.github.bounded-queries`** (object, AWF v0.28.0+) configures the AWF bounded-query subsystem for cross-repository private data access. When present, the agent may answer finite, pre-approved questions about the listed repositories using the generated `bounded-query` skill — without receiving raw source code. This is the preferred pattern for cross-repository workflows. Requires the AWF sandbox (`sandbox.agent.id: awf`). All optional fields use AWF defaults when omitted.

```yaml
tools:
github:
bounded-queries:
private-repos:
- repo: my-org/public-docs
sensitivity: public # public | internal | confidential | sealed
- repo: my-org/internal-service
sensitivity: internal
runtime: docker # optional; default: AWF default
timeout: 30 # optional; seconds; default: AWF default
memory-limit: 512m # optional; e.g. 512m, 2g; default: AWF default
interpreter: python3 # optional; default: AWF default
max-invocations: 32 # optional; default: AWF default
sandbox:
agent:
id: awf
```

Sensitivity levels: `public` (no restrictions), `internal` (internal-only audiences), `confidential` (restricted within org), `sealed` (highest restriction). The staging credential used to access private repositories must remain host-side and is never written to the lock file or exposed to the agent. Use bounded queries when the question has a finite, bounded answer; prefer this over granting a cross-repository token or checking out the private repository into the primary workspace.

- **`safe-outputs:`** - Safe output processing configuration. See [safe-outputs.md](safe-outputs.md) for complete documentation of all output types: `create-issue`, `create-discussion`, `add-comment`, `create-pull-request`, `push-to-pull-request-branch`, `close-issue`, `close-discussion`, `update-issue`, `update-pull-request`, `add-labels`, `remove-labels`, `replace-label`, `dispatch-workflow`, `call-workflow`, `create-code-scanning-alert`, `upload-asset`, `upload-artifact`, `assign-to-agent`, `assign-to-user`, and more.

Expand Down
2 changes: 2 additions & 0 deletions pkg/constants/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ func TestSpec_VersionConstraints_MinVersionValues(t *testing.T) {
{name: "AWFTokenSteeringMinVersion", constant: constants.AWFTokenSteeringMinVersion, expected: "v0.25.44"},
// From spec: CopilotNoAskUserMinVersion // "1.0.19"
{name: "CopilotNoAskUserMinVersion", constant: constants.CopilotNoAskUserMinVersion, expected: "1.0.19"},
// From spec: AWFBoundedQueriesMinVersion // "v0.28.0"
{name: "AWFBoundedQueriesMinVersion", constant: constants.AWFBoundedQueriesMinVersion, expected: "v0.28.0"},
}

for _, tt := range tests {
Expand Down
5 changes: 5 additions & 0 deletions pkg/constants/version_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ const AWFLegacySecurityMinVersion Version = "v0.27.32"
// future release that adds apiProxy.providers to awf-config-schema.json.
const AWFAPIProxyProvidersMinVersion Version = "v0.27.43"

// AWFBoundedQueriesMinVersion is the minimum AWF version that supports
// the boundedQueries section in awf-config.json.
// Workflows pinning an older AWF version must not emit this section.
const AWFBoundedQueriesMinVersion Version = "v0.28.0"

// DefaultGVisorVersion is the pinned gVisor release used by the compiler-generated
// install step. A specific dated release name is used instead of "latest" to ensure
// reproducible, verifiable installs. Each release provides SHA-512 files for
Expand Down
93 changes: 93 additions & 0 deletions pkg/workflow/awf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ type AWFConfigFile struct {
// APIProxy contains API proxy (LLM gateway) configuration.
APIProxy *AWFAPIProxyConfig `json:"apiProxy,omitempty"`

// BoundedQueries configures the AWF bounded-query subsystem for approved
// cross-repository private data access. Omitted when not configured.
BoundedQueries *AWFBoundedQueriesConfig `json:"boundedQueries,omitempty"`

// Container contains container execution configuration.
Container *AWFContainerConfig `json:"container,omitempty"`

Expand All @@ -181,6 +185,50 @@ type AWFConfigFile struct {
Chroot *AWFChrootConfig `json:"chroot,omitempty"`
}

// AWFBoundedQueriesConfig is the "boundedQueries" section of the AWF config file.
// It controls the bounded-query subsystem that allows finite, pre-approved questions
// about private repositories. All optional fields are omitted when unset so that
// AWF remains the source of truth for default values.
type AWFBoundedQueriesConfig struct {
// Enabled must be true when boundedQueries is present in the config.
// gh-aw always sets this to true when the section is generated.
Enabled bool `json:"enabled"`

// PrivateRepos is the list of private repositories approved for bounded-query access.
PrivateRepos []*AWFBoundedQueryPrivateRepo `json:"privateRepos,omitempty"`

// Runtime is the container runtime for bounded-query script execution (e.g. "docker").
// Optional; when omitted AWF uses its default.
Runtime string `json:"runtime,omitempty"`

// Timeout is the maximum execution time in seconds for a single invocation.
// Optional; when omitted AWF uses its default.
Timeout int `json:"timeout,omitempty"`

// MemoryLimit is the memory limit for bounded-query container execution (e.g. "512m").
// Optional; when omitted AWF uses its default.
MemoryLimit string `json:"memoryLimit,omitempty"`

// Interpreter is the script interpreter for bounded-query execution (e.g. "python3").
// Optional; when omitted AWF uses its default.
Interpreter string `json:"interpreter,omitempty"`

// MaxInvocations is the maximum number of bounded-query invocations per run.
// Optional; when omitted AWF uses its default.
MaxInvocations int `json:"maxInvocations,omitempty"`
}

// AWFBoundedQueryPrivateRepo describes a single private repository approved for
// bounded-query access, with its confidentiality classification.
type AWFBoundedQueryPrivateRepo struct {
// Repo is the "owner/repo" slug of the approved private repository.
Repo string `json:"repo"`

// Sensitivity is the confidentiality classification.
// Accepted values: "public", "internal", "confidential", "sealed".
Sensitivity string `json:"sensitivity"`
}

// AWFRunnerConfig is the "runner" section of the AWF config file.
// It provides a single stable contract between gh-aw and AWF for runner topology
// detection, letting AWF resolve all internal details (network isolation, sysroot
Expand Down Expand Up @@ -689,6 +737,16 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) {
}
awfConfigLog.Printf("Logging section: proxyLogsDir=%s, auditDir=%s", awfConfig.Logging.ProxyLogsDir, awfConfig.Logging.AuditDir)

// ── Bounded queries section ──────────────────────────────────────────────
if bq := extractBoundedQueriesConfig(config.WorkflowData); bq != nil {
if awfSupportsBoundedQueries(firewallConfig) {
awfConfig.BoundedQueries = bq
awfConfigLog.Printf("Bounded queries section: %d private repo(s)", len(bq.PrivateRepos))
} else {
awfConfigLog.Printf("Skipping boundedQueries: AWF version %q requires at least %s", getAWFImageTag(firewallConfig), constants.AWFBoundedQueriesMinVersion)
}
}

jsonStr, err := jsonutil.MarshalCompactNoHTMLEscape(awfConfig)
if err != nil {
return "", fmt.Errorf("failed to marshal AWF config to JSON: %w", err)
Expand Down Expand Up @@ -899,6 +957,41 @@ func extractModelCostProviders(workflowData *WorkflowData) map[string]any {
return clone
}

// extractBoundedQueriesConfig returns an AWFBoundedQueriesConfig populated from
// tools.github.bounded-queries, or nil when the field is absent.
// Only fields explicitly set in frontmatter are included; optional fields that
// were not specified are omitted so that AWF remains the source of truth for defaults.
func extractBoundedQueriesConfig(workflowData *WorkflowData) *AWFBoundedQueriesConfig {
if workflowData == nil {
return nil
}
if workflowData.ParsedTools == nil || workflowData.ParsedTools.GitHub == nil {
return nil
}
bq := workflowData.ParsedTools.GitHub.BoundedQueries
if bq == nil {
return nil
}

awfBQ := &AWFBoundedQueriesConfig{
Enabled: true,
Runtime: bq.Runtime,
Timeout: bq.Timeout,
MemoryLimit: bq.MemoryLimit,
Interpreter: bq.Interpreter,
MaxInvocations: bq.MaxInvocations,
}

for _, r := range bq.PrivateRepos {
awfBQ.PrivateRepos = append(awfBQ.PrivateRepos, &AWFBoundedQueryPrivateRepo{
Repo: r.Repo,
Sensitivity: r.Sensitivity,
})
}

return awfBQ
}

// getRunnerTopology extracts the runner topology string from WorkflowData.
// Returns an empty string when no topology is configured.
func getRunnerTopology(workflowData *WorkflowData) string {
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/awf_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,12 @@ func awfSupportsAPIProxyProviders(firewallConfig *FirewallConfig) bool {
return awfVersionAtLeast(firewallConfig, constants.AWFAPIProxyProvidersMinVersion)
}

// awfSupportsBoundedQueries returns true when the effective AWF version supports
// the boundedQueries section in awf-config.json.
func awfSupportsBoundedQueries(firewallConfig *FirewallConfig) bool {
return awfVersionAtLeast(firewallConfig, constants.AWFBoundedQueriesMinVersion)
}

// buildArcDindChrootConfigPatchBody returns the Node.js command that patches the AWF
// config file with chroot.binariesSourcePath and chroot.identity.*. It is designed to be
// embedded inside a bash if-block that already guards on DOCKER_HOST=tcp://...
Expand Down
Loading
Loading