Skip to content

Allow dependency additions without bypassing protected-file safety #1748

Closed

Description

@bradygaster

Problem

Squad currently cannot produce a pull request for any task that requires adding or changing a package dependency.

Examples:

  • Add Serilog
  • Add pytest
  • Add lodash
  • Add a NuGet central package version
  • Add or update a Python, npm, Go, Ruby, Java, Gradle, Deno, Bun, or Elixir dependency

After #1746 / PR #1747, the implement-worker uses:

protected-files:
  policy: fallback-to-issue
  exclude:
    - README.md

That fixed the signed-commit hard failure, but it also made the consequence explicit: dependency-manifest changes fall back to a protected-file review issue instead of a PR.

That is too limiting. Supply-chain risk is real, but a coding agent that cannot add dependencies is missing a common and legitimate class of work.

Why this is blocked today

gh-aw protects dependency manifests by default. The default protected-files list covers manifests and lockfiles across ecosystems: npm/yarn/pnpm/Bun/Deno, Python, NuGet, Go modules, Ruby gems, Maven/Gradle, and Elixir mix files.

Important mechanics:

  • Matching is by basename at any depth, not glob. package.json is protected whether it sits at the repo root or under src/Foo/package.json.
  • The default list is injected unconditionally.
  • The list is not replaceable, only narrowable, through protected-files.exclude.
  • request_review is not viable with signed commits — the signed-commit push path re-validates protected-file output and only accepts allow. That was finding F.
  • excluded-files is a different mechanism: it strips paths before protected-file evaluation, and is not the right model for this.

So our current practical choices are: keep dependency files protected and fall back to issue, or exclude selected dependency files from protection.

The actual tension

The protected-file guard should prevent routine agent output from quietly normalizing high-leverage changes — dependency graph, install/build/test execution, governance, release provenance, workflow/security posture.

But today we are preventing more than that. We are not just preventing silent dependency changes; we are preventing the agent from producing any PR for legitimate dependency-addition work.

The goal should be: dependency changes are explicit, reviewable, and intentional — not impossible.

Design options

Option 1: Narrow protected-files.exclude per ecosystem

For repos that opt in, exclude selected dependency manifests from protection.

protected-files:
  policy: fallback-to-issue
  exclude:
    - README.md
    - package.json
    - package-lock.json
    - pyproject.toml
    - requirements.txt
    - Directory.Packages.props

Pros: Simple. Works with gh-aw as-is. Lets common dependency tasks produce PRs. Tailorable per repo/ecosystem.

Cons: Basename-anywhere matching means excluding package.json affects every nested package.json. Coarse-grained — cannot distinguish "add lodash" from a riskier script or package-manager change. Review safety depends on Squad presentation, labels, and reviewer discipline.

Option 2: Make dependency-change support an adoption-time repo choice

During Squad adoption/preflight, ask whether the repo allows dependency-addition PRs. Modes: strict (always fall back to issue), ecosystem opt-in, or permissive (allow manifests/lockfiles but mark PRs clearly).

This should live near the same adoption-time surface as finding E — the preflight for Settings → Actions → Allow GitHub Actions to create and approve pull requests.

Pros: Makes this a conscious repo-owner decision. Avoids one global policy pretending to fit every repo. Gives us a natural place to explain the risk.

Cons: More setup complexity. Requires encoding and persisting repo policy. Existing repos need migration/default behavior.

Option 3: Dedicated dependency-change flow

Keep dependency files protected by default, but add a first-class dependency-change workflow that requires the issue to explicitly request a dependency addition, generates a PR with a clear dependency-change label, includes package name/version/ecosystem rationale, surfaces install/test evidence, and requires human approval before merge.

Under the hood this still likely uses protected-files.exclude, but only for repos/workflows that choose this path.

Pros: Best matches the real risk model. Makes dependency changes conspicuous instead of routine. Gives reviewers the right context.

Cons: More workflow design work. Still constrained by basename-level exclude behavior. Needs careful UX so it does not feel like a failed worker.

Option 4: Treat manifests and lockfiles differently

Variants: allow manifest edits and require lockfile regeneration and review; allow lockfile updates only when paired with manifest changes; protect package-manager config more strongly than ordinary manifests; allow app-level manifests but keep governance files like NuGet.Config protected.

Pros: More precise than blanket allow/block. Reflects ecosystem-specific risk. Prevents dangerous config/provenance changes while allowing ordinary package adds.

Cons: Hard to express with a basename matcher. Ecosystems differ significantly. Could become confusing without strong defaults.

Recommendation

Adopt Option 2 plus Option 3.

Make dependency-addition support an adoption-time repo choice, then route enabled repos through a dedicated dependency-change path that produces PRs with explicit review context.

Default posture stays conservative for existing repos: dependency changes fall back to issue unless the repo opts in. For opted-in repos, Squad narrows protected-files.exclude only for the selected ecosystem files and makes resulting PRs visibly dependency-related through title, body, labels, and checklist.

This keeps the safety principle from #1746 / PR #1747, but stops treating package addition as prohibited work.

Open questions

  1. Should dependency-addition PRs be opt-in per repo, or allowed by default for Squad-managed repos?
  2. Which ecosystems should we support first — npm, NuGet, Python, or all common manifest types?
  3. Should lockfiles be allowed in the same mode as manifests, or handled separately?
  4. Are package-manager config files like NuGet.Config, .npmrc, or registry config always protected even when dependency manifests are allowed?
  5. Should dependency-change PRs require a special label or reviewer checklist before merge?
  6. Should the adoption preflight combine this with finding E so repo owners make one explicit "agent can create PRs / agent can change dependencies" decision?

Related

Activity

  1. bradygaster commented on Aug 19, 2026

    @bradygaster
    CollaboratorAuthor

    Decision on open question 1 — allowed by default, opt-out rather than opt-in.

    Package additions should be permitted by default. Repos that want to prohibit dependency changes opt out explicitly.

    Rationale: blocking agents from adding npm / NuGet / pip packages is a hostile default. The common case is legitimate — an agent implementing a feature frequently needs a package, and a hard prohibition turns ordinary work into a dead end instead of a reviewable change. Safety comes from the change being explicit and reviewable, not from it being impossible.

    This inverts the Option 2 recommendation above: the adoption-time configuration surface exposes a disable toggle, not an enable toggle.

    Consequences to fold into the design:

    • Assume default-on. Build the deny path as the configurable surface.
    • Existing repos inherit the permissive default rather than staying on the conservative one. That is a behavior change for repos already running Squad and needs to be called out in release notes.
    • Option 3 (dedicated dependency-change flow with rationale and install/test evidence in the PR) still applies — it is what makes default-on safe. The PR must make the dependency change obvious to a reviewer.
    • Open questions 2–6 should be re-read through a default-on lens; several were framed around gating the enable path and may collapse.
  2. bradygaster commented on Aug 20, 2026

    @bradygaster
    CollaboratorAuthor

    Flight sequencing note — revised 2026-08-20.

    Wave: 2.

    Depends on:

    Blocks: nothing else in this stream directly.

    Safe to run in parallel with: #1756 and #1759 after #1762 is decided; #1770, #1760, #1772, and #1768 at any time. Be cautious with #1761 only if both edits rewrite the same gh-aw guide setup prose.

    Why / evidence: #1748's policy lives in the implement worker protected-file safe-output area: workflows/squad-implement-worker.md lines 170-181 document the current fallback-to-issue posture and only exclude README.md; lines 178-196 define the structural protected/excluded-file guard. #1762's related setup/preflight material is elsewhere: docs/src/content/docs/guide/gh-aw.md lines 63-82 already document the Actions setting, and workflows/squad.md lines 420-451 are the connect/adopt paths where a shared preflight would belong. No workflows/squad.md hunk overlaps #1756/#1759's prompt blocks.

  3. babyblueviper1 commented on Aug 20, 2026

    @babyblueviper1

    On the basename-matching mechanics: worth separating two questions that the current protected-files check conflates into one policy decision.

    "Is this file a dependency manifest" and "is this specific change to it risky" are different axes. A new package name being introduced for the first time (unknown provenance, unaudited transitive tree, name-squatting/typosquat surface) is a materially different risk than a version bump on a package the repo already depends on and has presumably already vetted once. Basename-at-any-depth matching only answers the first question — it can't distinguish "lodash": "^4.17.21" -> "^4.17.23" from adding a brand-new, never-seen-before package name to the same file, even though the second is the actual supply-chain attack surface (event-stream, ua-parser-js, node-ipc were all typosquat/compromise incidents on additions or takeovers, not routine bumps).

    That argues for a third axis inside Option 4 rather than a fifth option: for manifest-diff review, classify each changed line as new-dependency / version-bump / removed, not just "manifest touched." A repo could then reasonably set version-bump: allow, new-dependency: fallback-to-issue as defaults — which gets close to Option 2's opt-in spirit without needing a separate adoption-time toggle, since the risk-appropriate default falls naturally out of the diff classification rather than a binary per-repo setting.

    The open question this doesn't answer: whether a lockfile-only change (no manifest touch, e.g. a transitive dep updating under a caret range) should classify the same way — that's a real gap in a diff-based classifier that a basename check sidesteps by being coarse in the first place.

  4. added
    squadSquad triage inbox — Lead will assign to a member
    on Aug 20, 2026
  5. added
    wave:3-laterWave 3 — backlog, future
    and removed
    wave:2-soonWave 2 — next 2-4 weeks
    on Aug 20, 2026
  6. bradygaster commented on Aug 25, 2026

    @bradygaster
    CollaboratorAuthor

    Flight Decision — #1748: Dependency-Addition Support

    Status: APPROVED — IMPLEMENTATION-READY (2026-08-25; proceed under the finalized slices and acceptance criteria below)
    Prepared by: Flight (Lead)
    Date: 2026-08-25


    Executive Recommendation

    Introduce a dedicated squad-deps-worker compiled with dependency manifest exclusions, and keep the existing squad-implement-worker unchanged (all manifests remain fallback-to-issue on the general path). Per-repo opt-out is prompt-enforced via .squad/config.json key "squadDeps" — not structural — because gh-aw protected-files.exclude is resolved at compile time (safe_outputs_config_generation.go:sliceutil.Exclude()) and cannot read a runtime repo file.

    Brady's decision on default-on is fixed. This decision synthesizes all specialist inputs. All four design choices have been resolved by Brady (see §Resolved Decisions below); implementation may proceed.

    Rejected alternatives

    Alternative Reason rejected
    protected-files: allowed Removes ALL protection — CODEOWNERS, SECURITY.md, NuGet.Config become agent-writeable in one PR
    Per-path exclude (e.g., src/*/package.json only) Not supported; basename-anywhere is the only matching semantics (ADR-26339; manifest_file_helpers.cjs:301-305: file.split("/").pop())
    request_review policy Hard-fails on signed commits — checkFileProtectionPostApply throws Signed-commit payload violates file-protection policy for anything other than allow; confirmed #1746
    Opt-in per-repo Brady reversed this explicitly: "blocking agents from adding packages is a hostile default" (#1748 comment 2026-08-19T21:42:52Z)
    excluded-files expansion Wrong mechanism — strips paths from patch before protected-file evaluation; manifests would not appear in PR diff at all
    Diff-classification (new-dep vs. bump) Technically correct supply-chain framing (babyblueviper1, 2026-08-20) but not implementable at gh-aw's basename-only layer; not a Squad design choice but a gh-aw technical constraint. Defer to wave:3+ or upstream gh-aw feature request
    Runtime .squad/config.json structural gate Technical constraint: protected-files.exclude is compile-time, baked into .lock.yml; there is no hook for the runtime worker to read .squad/config.json and conditionally change the compiled exclusion set
    Single-worker with prompt-only guard A prompt paragraph alone does not prevent the general implementation path from silently becoming the dependency path once manifests are excluded from protected-files. No structural mechanism prevents a general workflow_dispatch from touching manifests once exclusions are compiled in. See §Dedicated Workflow / Path Authority.

    File-Class Policy Matrix

    Class Default posture Always-protected Notes
    Dependency manifest (package.json, pyproject.toml, go.mod, pom.xml, etc.) Excluded → PR produced (dependency path only; general path: fallback-to-issue) — Basename-anywhere: affects ALL paths at ALL depths; cannot scope to one nested package
    Lockfile (package-lock.json, go.sum, Gemfile.lock, yarn.lock, etc.) Excluded → PR produced (dependency path only; general path: fallback-to-issue) — Pair with manifest change when possible; lockfile-only changes allowed — mandatory PR callout AND frozen-install validation evidence (exit 0) are normative requirements
    Registry/install config (NuGet.Config, bunfig.toml, .npmrc, .yarnrc.yml) Protected → fallback-to-issue ✓ Registry injection attack surface — controls endpoints and credentials, not package declarations
    SDK/tool pin (global.json) Protected → fallback-to-issue ✓ SDK version pin; not a package dependency
    Governance docs (CODEOWNERS, SECURITY.md, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md, DESIGN.md) Protected → fallback-to-issue ✓ Modifying CODEOWNERS silently removes review gates
    Agent instruction files (AGENTS.md) Protected → fallback-to-issue ✓ Engine-injected at compile time via copilot_engine.go:147-151; stays protected regardless of exclude list
    Build scripts (setup.py, setup.cfg) Excluded → PR produced (dependency path only) — Listed as Python manifests in runtime_definitions.go; setup.py executes code on install — PR body must call this out explicitly
    Gradle properties (gradle.properties) Excluded → PR produced (dependency path only) — Common dep-related use (ext {} blocks); reviewer responsibility for build-property changes
    Vendored/generated dependency content (node_modules/, vendor/, generated caches, build artifacts) Never authorized by the dependency path ✓ Must not appear in PR diff. Worker must structurally strip or deny these paths (excluded-files expansion is the correct mechanism). Any exception requires a separate, explicitly scoped human-owned authority outside Squad's dependency path.

    bunfig.toml ruling (DECIDED): Keep protected. runtime_definitions.go lists it under ManifestFiles alongside package.json and bun.lockb, but it controls Bun registry endpoints and install behavior — identical risk profile to .npmrc. Flight recommendation: do not exclude. Brady approved: keep protected.


    Concrete Config Schema

    protected-files.exclude — lives in workflows/squad-deps-worker.md (new file; see §Dedicated Workflow)

    This is the only structural mechanism available. The compiled .lock.yml bakes exclusions in at gh aw add/gh aw compile time. No runtime repo file can change the compiled exclusion set. The general worker (squad-implement-worker.md) retains its current config unchanged; only the dependency worker carries manifest exclusions.

    protected-files:
      policy: fallback-to-issue
      exclude:
        - README.md
        # --- Wave 1: npm/yarn/pnpm + NuGet CPM + Go ---
        # network.allowed coverage: node (squad-implement-worker.md:41), dotnet (:38), go (:39)
        # npm / yarn / pnpm
        - package.json
        - package-lock.json
        - yarn.lock
        - pnpm-lock.yaml
        - npm-shrinkwrap.json
        # .NET — manifest only; NuGet.Config and global.json stay protected
        - Directory.Packages.props
        # Go
        - go.mod
        - go.sum
        # --- Wave 2 (separate PR): Python/uv + Ruby ---
        # network.allowed coverage: python (:42), ruby (:43)
        # Prerequisites: setup.py risk callout defined; uv.lock regeneration confirmed
        # - requirements.txt
        # - Pipfile
        # - Pipfile.lock
        # - pyproject.toml
        # - setup.py
        # - setup.cfg
        # - poetry.lock
        # - uv.lock
        # - Gemfile
        # - Gemfile.lock
        # --- Wave 3 (separate PR): Maven/Gradle + Deno/Bun + Elixir ---
        # Deno/Bun/Elixir NOT in network.allowed (squad-implement-worker.md:34-43)
        # Requires network.allowed extension + Brady approval before inclusion

    Always-protected regardless of opt-out or ecosystem scope: NuGet.Config, global.json, bunfig.toml, CODEOWNERS, SECURITY.md, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md, DESIGN.md, AGENTS.md, .npmrc, .yarnrc.yml.

    allowed-files gap (Slice S1, required before S2): The current allowed-files section (squad-implement-worker.md:62-169) uses extension-only patterns. Extensionless manifests — go.mod, go.sum, Gemfile, Gemfile.lock, Pipfile, Pipfile.lock, yarn.lock, bun.lockb, mix.exs, mix.lock, pom.xml — match no existing pattern (confirmed by FIDO, test/gh-aw-implement-workflow.test.ts:62-169 analysis). These basenames must be added as explicit allowed-files entries in the new dependency worker. S1 and S2 must land in the same PR, atomically, to prevent a state where files are unprotected but also un-allowed.

    Note: allowed-files and protected-files are independent checks (manifest_file_helpers.cjs:checkFileProtection). A file matching *.json in allowed-files still hits the basename protected-files check — allowed-files does not suppress it. The exclude list is the exclusive unblock mechanism.

    Per-repo opt-out (prompt-level only)

    Config key: "squadDeps" in .squad/config.json. Follows existing camelCase convention (workflows/squad.md squad-connect step 3, squad-adopt step 3). One new key; no new config schema.

    Exact schema and fail-closed semantics:

    squadDeps value Behavior
    Key absent Allow (Brady's default-on)
    Exact string "allow" Allow
    Exact string "deny" Deny — agent stops, posts comment citing config key, and stops
    Any other string Deny (fail-closed)
    Boolean, number, array, object, or null Deny (fail-closed)
    File unreadable or malformed JSON Deny (fail-closed)

    Prompt instruction to add to squad-deps-worker.md (Implement section, step 1a):

    DEPENDENCY CHANGE GUARD (step 1a). Before adding, removing, or updating any package
    dependency:
      1. Read .squad/config.json.
      2. If the file cannot be read or contains malformed JSON → DENY. Post a comment
         on the issue stating the config is unreadable and stop.
      3. If key "squadDeps" is absent → ALLOW (default-on per Brady decision).
      4. If "squadDeps" is the exact string "allow" → ALLOW.
      5. If "squadDeps" is the exact string "deny" → DENY. Post a comment citing
         `.squad/config.json squadDeps: "deny"` and stop.
      6. Any other value (other string, boolean, number, null, object, array) → DENY
         (fail-closed). Post a comment stating the value is unrecognized and stop.
    

    Runtime config does NOT dynamically change compiled exclusions. The compiled .lock.yml always reflects the exclusion list baked in at compile time. The prompt guard above controls agent behavior only — it does not alter which files gh-aw's structural protection gates apply to.

    Explicit limitation to state in docs and release notes: This guard is prompt-level, not structural. A malfunctioning or adversarially prompted agent could bypass it. The structural fallback is the PR review process: dependency-change PRs are visible, labeled, and subject to whatever branch protection and CODEOWNERS rules exist in the consuming repo.


    Dedicated Workflow / Path Authority

    Decision: a genuinely distinct squad-deps-worker is the correct design. DECIDED — Brady approved.

    A prompt paragraph inside the general implementation worker does not prevent that worker from silently becoming the dependency path once manifests are globally excluded from its protected-files. Once exclusions are compiled in, any task dispatched to the worker — including a general workflow_dispatch — can touch manifests. There is no structural mechanism beyond the prompt guard that prevents this.

    Structural design (recommended):

    • squad-implement-worker.md retains its current protected-files.policy: fallback-to-issue for ALL manifests with no exclusions added. General implementation tasks continue to fall back to issue for any manifest write — unchanged from today.
    • squad-deps-worker.md (new file) is compiled with Wave 1 manifest exclusions. It is dispatched only by explicit dependency intent. It carries the dependency-change guard (step 1a) and full PR presentation requirements.
    • workflow_dispatch for a general task hits squad-implement-worker (no manifest access). Only dependency-intent dispatch reaches squad-deps-worker.

    Why this is stronger than a single-worker with prompt-only guard:

    • The general worker's fallback-to-issue protection for manifests is structural (compiled into .lock.yml) and cannot be bypassed by prompt injection, misfired dispatch, or agent miscommunication.
    • Dispatch provenance is clear: a manifest-touching PR can only originate from squad-deps-worker, not from a general implementation run.
    • workflow_dispatch authorization: direct workflow_dispatch to squad-deps-worker requires explicit human action with contents: write permission. Squad cannot prevent this (operator action outside the control plane), but it is auditable. The general worker has no such surface.

    Fail-closed behavior (deps worker):

    • squadDeps: deny (or unrecognized value, unreadable config) → agent stops and comments on issue; no PR created; no manifest touched
    • squadDeps: allow (or absent) → agent proceeds; excluded manifests receive signed commits normally (excluded files return action: allow from checkFileProtectionPostApply)
    • Any non-excluded file (e.g., NuGet.Config, AGENTS.md) → fallback-to-issue still fires regardless

    Rejected alternative now reversed: "Separate dependency workflow — same protection model; adds registration complexity for no structural gain." This was incorrect. A separate worker compiled without manifest exclusions provides genuine structural isolation. The complexity cost is one new workflow file and one additional gh aw add step at adoption. The structural gain is that the general path retains its current fallback-to-issue protection for manifests unconditionally. Flight reverses this rejection. (DECIDED — Brady approved dedicated worker; see §Resolved Decisions.)


    PR Presentation Rules

    Required for every PR produced by squad-deps-worker touching a dependency manifest or lockfile:

    • Title prefix: deps: (example: deps: add lodash 4.17.21 for string utilities (#42))
    • Label: dependency-change
      • Exact label name: dependency-change; exact description: Dependency manifest or lockfile change produced by squad-deps-worker; exact color: #e6f3ff (blue-tinted, visually distinct from squad purple)
      • Provisioning (DECIDED — Brady approved): The dedicated squad-deps-worker creates the label if absent in the consuming repo. If provisioning fails (insufficient permissions), this is treated as a hard failure — the worker stops and posts a comment on the issue stating that the required dependency-change label could not be established. A PR is NOT created without the label. Repo operators must grant the agent label-provisioning permission or pre-create the label. Adoption guide must document this requirement.
    • Body section ## Dependency Changes containing:
      • Each package: name, version, ecosystem, whether new (never previously in manifest) or update (version bump only)
      • Rationale: why this issue requires this package (cite issue title/number)
      • Direct vs. transitive classification
      • Install/lock command output (frozen install evidence: exit 0, e.g., npm ci --frozen-lockfile)
      • Frozen build + test output if available
      • Audit output if available (npm audit --audit-level=moderate, pip audit, govulncheck ./..., etc.)
      • Reviewer callout: "New package names (not previously in this manifest) are the primary supply-chain risk surface. Please inspect them specifically. Version bumps on existing packages are lower risk."
      • Lockfile-only callout (normative — required when no manifest changed): "This PR modifies only a lockfile — no package was explicitly added or removed. This is a transitive dependency update. Frozen-install validation evidence is required in this PR body (exit 0, e.g., npm ci --frozen-lockfile). Verify locally by running [install command] --frozen-lockfile."
      • No vendored content confirmation: "This PR does not include node_modules/, vendor/, or generated cache directories. If any appear in the diff, do not merge."

    Human approval (Category C — external): Squad cannot enforce required human approval before merge. That requires branch protection and/or CODEOWNERS review rules in the consuming repo. The PR body must note: "We recommend enabling required-reviewer rules or the GitHub Dependency Review action for manifest files in your branch protection settings." Squad's PRs are subject to whatever protection rules exist; Squad cannot bypass them.


    Ecosystem Scope and Expansion Criteria

    Decision: phased rollout across three waves. Evidence basis: workflows/squad-implement-worker.md:34-43 (network.allowed).

    The worker's network.allowed list (squad-implement-worker.md:34-43) includes: defaults, containers, dotnet, go, java, node, python, ruby, rust. It does not include deno, bun, or elixir. Without a network.allowed token, the agent cannot run deno install, bun install, or mix deps.get to regenerate lockfiles in the CI environment. Shipping manifest exclusions for those ecosystems before network.allowed is extended means the agent can edit manifests but cannot produce a verifiable frozen lockfile. This is a material gap.

    Wave 1 (initial PR — squad-deps-worker.md creation): npm/yarn/pnpm + NuGet central package management + Go

    • network.allowed coverage: node (line 41), dotnet (line 38), go (line 39)
    • Clear manifest/lockfile/registry-config boundaries; NuGet.Config and global.json remain protected in all waves

    Wave 2 (separate PR, after Wave 1 merged and validated): Python/uv + Ruby

    • network.allowed coverage: python (line 42), ruby (line 43)
    • Prerequisites: define PR body callout for setup.py code-execution risk (executes on install); confirm uv.lock regeneration command; confirm bundle install --frozen evidence format
    • Acceptance criterion: at least one integration test per ecosystem proving lockfile regeneration succeeds in the gh-aw environment

    Wave 3 (separate PR, after Wave 2 merged): Maven/Gradle + Deno/Bun + Elixir

    • Maven/Gradle: covered by java (line 40) in network.allowed, but settings.gradle registry-config boundary and Gradle plugin portal trust must be defined before including
    • Deno: not in network.allowed — requires network.allowed: [deno] extension in squad-deps-worker.md AND explicit Brady approval
    • Bun: not in network.allowed — same requirement; additionally bunfig.toml is confirmed protected (DECIDED — see §Resolved Decisions feat: worktree awareness, Scribe auto-commit, and decision consolidation #1)
    • Elixir: not in network.allowed — requires network.allowed: [elixir] extension AND explicit Brady approval
    • Acceptance criterion per ecosystem: verified network.allowed entry, defined frozen-install command, structural test, integration test

    Haskell and Rust: Not included in any wave above without explicit verification and Brady scoping decision.

    • Rust (cargo): rust IS in network.allowed (line 44). Cargo.toml/Cargo.lock are well-defined. Can be added to Wave 3 with explicit Brady approval and a structural test.
    • Haskell (stack): NOT in network.allowed. Requires network.allowed extension + explicit Brady decision + integration test.

    Objective expansion criteria (any ecosystem, any wave):

    1. The ecosystem's network.allowed token is present in squad-deps-worker.md
    2. A frozen-install/lockfile-regeneration command is defined and documented
    3. Manifest/lockfile basenames added to both protected-files.exclude AND allowed-files atomically (S1+S2 pattern)
    4. Structural test T1-variant verifies new basenames appear in the exclude list; mutation M1-variant causes named failure
    5. Behavioral integration test verifies lockfile regeneration succeeds in the CI environment for the new ecosystem

    Migration / Backward Compatibility

    Breaking behavior change for all repos already running Squad post-#1747:

    Before After
    "Add lodash package" → fallback to review issue (via squad-implement-worker) "Add lodash package" (if routed via squad-deps-worker) → PR with dependency-change label
    Directory.Packages.props change → issue Directory.Packages.props change via deps worker → PR
    All dep manifests fallback-to-issue on general path General worker: all dep manifests still fallback-to-issue (unchanged). Deps worker: Wave 1 manifests excluded

    Required actions:

    1. Release notes must explicitly call out the new squad-deps-worker dispatch path. General implementation tasks continue to fall back to issue for manifest writes. Dependency-intent tasks must be explicitly dispatched to the new worker.
    2. Opt-out: add "squadDeps": "deny" to .squad/config.json to prevent the deps worker from touching manifests.
    3. Recompile required for each new worker adoption: gh aw add bradygaster/squad/workflows/squad-deps-worker.md@dev. The general worker (squad-implement-worker.md) is unchanged and does not need recompilation for this feature. The installed .lock.yml embeds the compile-time exclude list — editing the source .md in this repo alone has no effect on already-installed workflows (confirmed: PR fix(worker): use fallback-to-issue for protected files #1747 body, "Workflow source changes must be recompiled").

    Threat Model

    Threat Squad mitigation (A = policy, B = gh-aw technical, C = external) Residual risk
    Dependency confusion / typosquatting PR body requires new package names explicitly listed with new-vs-update classification; reviewer callout to inspect new names (A) Agent cannot diff-classify reliably at prompt level; GitHub Dependency Review action (C) handles automated vuln/advisory scanning — Squad directs, reviewer decides
    Registry redirection NuGet.Config, bunfig.toml, .npmrc always protected — deps worker structurally cannot touch them (B+A) Structural protection at gh-aw layer; not overridable by prompt
    General path → manifest bypass squad-implement-worker retains fallback-to-issue for ALL manifests (no exclusions); only squad-deps-worker has exclusions (B) Direct workflow_dispatch to squad-deps-worker by a human with contents: write is an operator action outside Squad's control plane
    Vendored/generated content in PR (node_modules/, vendor/, caches) excluded-files expansion in squad-deps-worker.md must strip these paths structurally; PR body requires explicit no-vendored-content confirmation (B+A) Prompt-level confirmation only if excluded-files expansion is not implemented; structural expansion is correct mechanism
    Malicious lifecycle scripts (setup.py, npm postinstall, Gradle tasks) PR body callout required for setup.py/setup.cfg changes; reviewer directed to inspect hooks (A) No sandbox; agent cannot execute scripts in safe mode; reviewer responsibility
    Lockfile tampering Lockfiles excluded alongside manifests; agent instructed to regenerate lockfile after manifest change; lockfile-only changes require mandatory PR callout AND frozen-install validation evidence (exit 0) in the PR body (A+normative) No cryptographic validation; reviewer must verify with npm ci --frozen-lockfile or equivalent
    Transitive drift Lockfile-only PR body callout required; reviewer directed to verify (A) Agent cannot enumerate full transitive tree from diff; GitHub Dependency Review (C) provides diff-level context
    Credentials in config files Registry/config files protected at structural level (B); .env-like files not in allowed-files Reviewer responsibility for manifest entries that embed registry tokens
    Prompt injection via issue body Issue bodies treated as untrusted per Squad adversarial handling rules; step 1a guard reads .squad/config.json, not issue body (A) Prompt-level guard; structural fallback is PR visibility and reviewer discipline
    Unrecognized squadDeps value Fail-closed: any value other than absent / "allow" / "deny" → deny immediately (A) None beyond structural

    Implementation Slices

    S1, S2 must land atomically in one PR (new squad-deps-worker.md, Wave 1 scope). {S3, S4} in parallel after S1+S2. S5 after S3. S6 accompanies each slice (TDD preferred). Waves 2 and 3 are separate PRs with separate Brady approvals.

    Slice Description Deps Files Measurable acceptance criteria
    S1 Create squad-deps-worker.md with Wave 1 allowed-files for extensionless manifest/lockfile basenames none workflows/squad-deps-worker.md (new) T3: go.mod, go.sum, yarn.lock, package-lock.json appear in allowed-files; new test asserts squad-implement-worker.md has NO manifest exclusions
    S2 Add Wave 1 ecosystem exclude list to protected-files.exclude in deps worker S1 (atomic) workflows/squad-deps-worker.md T1: exclude list covers npm/yarn/pnpm + NuGet CPM + Go; T2: NuGet.Config, .npmrc, bunfig.toml NOT in exclude; T4: policy is fallback-to-issue; T6: compiled .lock.yml config reflects exclusions; T7: package-lock.json in both allowed-files and exclude
    S3 Opt-out wiring and exact config schema in workflows/squad.md connect/adopt S2 workflows/squad.md (~lines 420-451) T8: no opt-in flag required; T9: opt-out key documented; T12: any unrecognized value → deny (fail-closed per exact schema above)
    S4 Deps worker prompt: dependency-change PR requirements (label, body, evidence, no-vendored-content) S2 workflows/squad-deps-worker.md (prompt body) T5: opt-out guard with exact schema present; T10: label instruction present; T11: lockfile-only callout present; T_vendor: no-vendored-content instruction present
    S5 Adoption guide update S3 docs/src/content/docs/guide/gh-aw.md New section: default behavior, squad-deps-worker dispatch, opt-out key with exact schema, what stays protected; #1762 closed/docs-only acknowledged; dependency authorization surfaced alongside PR-creation capability for discoverability but documented as a separate policy decision
    S6 Tests T1–T13 + T_vendor with 5 required mutations none (TDD) test/gh-aw-deps-workflow.test.ts (new), test/gh-aw-implement-workflow.test.ts (add: general worker MUST NOT have manifest exclusions) All tests pass; M1–M5 mutations each cause a named failure; baseline 81 passed / 13 skipped preserved

    Assignment recommendation: S1+S2+S6 → Procedures (with FIDO writing/reviewing tests). S3 → Procedures. S4 → Procedures. S5 → PAO. All Wave 1 slices: wave:2.


    Dependency Authorization and Adoption Preflight (#1762)

    Decision: do not merge these concerns.

    #1762 is closed (docs-only): Brady decided documentation at docs/src/content/docs/guide/gh-aw.md:63-82 suffices for the Actions PR-creation setting; runtime preflight probe deferred to squad health (#1605). The dependency opt-out ("squadDeps": "deny") is a developer configuration choice, not a setup prerequisite.

    Adoption/setup answer: The dependency authorization setting (squadDeps) may be surfaced in the same setup/adoption report as the PR-creation capability (Actions write permission) for discoverability — both are relevant at onboarding time. However, they are separate policy decisions and must not be conflated: the Actions setting controls whether the agent can create PRs at all; squadDeps controls whether dependency manifests may be touched. These have different owners and different opt-out surfaces. #1762 being closed/docs-only does not imply the squadDeps decision is also docs-only — it requires implementation in S3 and S4.


    Resolved Decisions (Brady Approved — 2026-08-25)

    All four design choices are decided. Default-on is fixed (Brady decided previously). Implementation may proceed.

    # Decision Outcome
    1 bunfig.toml: exclude (treat as manifest) or keep protected (treat as registry config)? DECIDED — Keep protected. Controls Bun registry endpoints and install behavior — same risk profile as .npmrc. The runtime_definitions.go grouping with ManifestFiles reflects gh-aw's generic model, not Squad's product model. Brady approved.
    2 Lockfile-only changes (no manifest edit, e.g., transitive caret-range drift): allow or route to fallback-to-issue? DECIDED — Allow, with mandatory PR callout AND frozen-install validation evidence (exit 0) as normative requirements. Routing lockfile-only changes to fallback-to-issue is hostile for normal maintenance. Both the callout and frozen-install evidence are required, not optional. Brady approved.
    3 dependency-change label provisioning: worker creates if absent, or must be pre-provisioned? DECIDED — Dedicated squad-deps-worker creates the label if absent. Exact label: name dependency-change, description Dependency manifest or lockfile change produced by squad-deps-worker, color #e6f3ff. If the worker cannot establish the required label (insufficient permissions), it fails closed — stops and posts a comment; no PR is created without the label. Brady approved.
    4 Dedicated squad-deps-worker vs. single-worker with prompt-only guard? DECIDED — Dedicated squad-deps-worker. General worker retains fallback-to-issue for all manifests unconditionally; only deps worker has exclusions. Prompt-only separation in a single worker provides no structural isolation and cannot claim 'no bypass.' Brady approved.
  7. bradygaster commented on Aug 25, 2026

    @bradygaster
    CollaboratorAuthor

    Reopened after merge of PR #1883. That PR delivered S2 only (Wave-1 protected-files exclusions) and intentionally used Part of #1748; S3 dispatcher/config routing, S4 evidence/label behavior, and S5 docs/setup reporting remain outstanding. This issue must stay open until all acceptance criteria are satisfied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    gh-awGitHub Agentic Workflows integrationpriority:p1This sprintsquadSquad triage inbox — Lead will assign to a membertriagedIssue has been triagedtype:featureNew capabilitywave:3-laterWave 3 — backlog, future

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions