aiwg 2026.5.3 → 2026.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -1
- package/README.md +11 -0
- package/agentic/code/addons/aiwg-utils/manifest.json +2 -1
- package/agentic/code/frameworks/sdlc-complete/templates/aiwg-sections/02b-discover-first.md +42 -0
- package/agentic/code/frameworks/sdlc-complete/templates/aiwg-sections/manifest.json +7 -0
- package/agentic/code/frameworks/sdlc-complete/templates/copilot/copilot-instructions.md.aiwg-template +11 -0
- package/agentic/code/frameworks/security-engineering/manifest.json +2 -2
- package/agentic/code/frameworks/security-engineering/rules/RULES-INDEX.md +19 -4
- package/agentic/code/frameworks/security-engineering/rules/ci-action-pinning.md +145 -0
- package/agentic/code/frameworks/security-engineering/rules/dependency-source-policy.md +177 -0
- package/agentic/code/frameworks/security-engineering/skills/bun-release-age-gate/SKILL.md +193 -0
- package/agentic/code/frameworks/security-engineering/skills/ci-workflow-audit/SKILL.md +266 -0
- package/agentic/code/frameworks/security-engineering/skills/pnpm-release-age-gate/SKILL.md +218 -0
- package/agentic/code/frameworks/security-engineering/skills/security-engineering-quickref/SKILL.md +9 -0
- package/agentic/code/frameworks/security-engineering/skills/yarn-release-age-gate/SKILL.md +183 -0
- package/agentic/code/frameworks/security-engineering/templates/README.md +35 -0
- package/agentic/code/frameworks/security-engineering/templates/security-md-template.md +192 -0
- package/dist/src/cli/handlers/use.d.ts.map +1 -1
- package/dist/src/cli/handlers/use.js +21 -0
- package/dist/src/cli/handlers/use.js.map +1 -1
- package/dist/src/cli/project-isolation/detect.d.ts +8 -0
- package/dist/src/cli/project-isolation/detect.d.ts.map +1 -0
- package/dist/src/cli/project-isolation/detect.js +70 -0
- package/dist/src/cli/project-isolation/detect.js.map +1 -0
- package/dist/src/cli/project-isolation/index.d.ts +6 -0
- package/dist/src/cli/project-isolation/index.d.ts.map +1 -0
- package/dist/src/cli/project-isolation/index.js +7 -0
- package/dist/src/cli/project-isolation/index.js.map +1 -0
- package/dist/src/cli/project-isolation/signals.d.ts +4 -0
- package/dist/src/cli/project-isolation/signals.d.ts.map +1 -0
- package/dist/src/cli/project-isolation/signals.js +17 -0
- package/dist/src/cli/project-isolation/signals.js.map +1 -0
- package/dist/src/cli/project-isolation/warning.d.ts +20 -0
- package/dist/src/cli/project-isolation/warning.d.ts.map +1 -0
- package/dist/src/cli/project-isolation/warning.js +102 -0
- package/dist/src/cli/project-isolation/warning.js.map +1 -0
- package/dist/src/mcp/cli.mjs +6 -0
- package/dist/src/mcp/helpers.mjs +232 -0
- package/dist/src/mcp/server.mjs +41 -10
- package/dist/src/mcp/tools/command-run.mjs +101 -0
- package/dist/src/mcp/tools/discovery.mjs +291 -0
- package/dist/src/mcp/tools/subsystems.mjs +609 -0
- package/package.json +1 -1
- package/tools/agents/providers/hermes.mjs +338 -41
- package/tools/verify-hermes-citations.mjs +190 -0
- package/tools/warp/setup-warp.mjs +73 -1
package/CLAUDE.md
CHANGED
|
@@ -87,7 +87,7 @@ All 10 providers receive all 4 artifact types (agents, commands, skills, rules).
|
|
|
87
87
|
| Warp Terminal | `.warp/agents/` + WARP.md | `.warp/commands/` | `.warp/skills/` + `.agents/skills/` | `.warp/rules/` | `aiwg use sdlc --provider warp` |
|
|
88
88
|
| Windsurf | AGENTS.md | `.windsurf/workflows/` | `.windsurf/skills/` + `.agents/skills/` | `.windsurf/rules/` | `aiwg use sdlc --provider windsurf` |
|
|
89
89
|
| OpenClaw | `~/.openclaw/agents/` | `~/.openclaw/commands/` | `~/.openclaw/skills/` + `.agents/skills/` | `~/.openclaw/rules/` | `aiwg use sdlc --provider openclaw` |
|
|
90
|
-
| Hermes | AGENTS.md |
|
|
90
|
+
| Hermes | AGENTS.md + .hermes.md | via MCP `command-run` | `~/.hermes/skills/` (kernel) + `~/.hermes/skills/.aiwg/` (standard) | inlined in AGENTS.md + MCP `rule-list`/`rule-show` | `aiwg use sdlc --provider hermes` |
|
|
91
91
|
|
|
92
92
|
**Special cases:**
|
|
93
93
|
- **Codex**: Commands and skills deploy to home directory (`~/.codex/prompts/`, `~/.codex/skills/`) for user-level availability across all projects. `.codex/commands/` also deploys at project scope for operator visibility, but Codex commands are a static built-in enum in codex-rs and these files are not auto-scanned by the loader; AGENTS.md is the discovery bridge per ADR-1. Reference: `src/smiths/platform-paths.ts:23`.
|
|
@@ -95,6 +95,7 @@ All 10 providers receive all 4 artifact types (agents, commands, skills, rules).
|
|
|
95
95
|
- **Warp**: Agents and commands are also aggregated into `WARP.md` for single-file context loading
|
|
96
96
|
- **Windsurf**: Agents are aggregated into `AGENTS.md` at project root
|
|
97
97
|
- **OpenClaw**: All artifacts deploy to home directory (`~/.openclaw/`). First provider to support behaviors (`~/.openclaw/behaviors/`)
|
|
98
|
+
- **Hermes**: MCP sidecar architecture (`Hermes → MCP → AIWG`). Commands reach AIWG via `mcp_aiwg_command_run` (allow-listed). Standard skills (~385) live under `~/.hermes/skills/.aiwg/` and are recursively discovered; kernel skills (~9) live at the top level and are protected from the Curator (v0.12.0+) via the `.bundled_manifest`. The MCP server has ~12 core tools by default; ~45 additional via `AIWG_MCP_TOOLSETS=<csv>`. See `docs/integrations/hermes-quickstart.md`.
|
|
98
99
|
|
|
99
100
|
## Writing Principles
|
|
100
101
|
|
package/README.md
CHANGED
|
@@ -49,6 +49,17 @@ AIWG is a deployment tool and support utility for AI context. At its core, `aiwg
|
|
|
49
49
|
|
|
50
50
|
Around that core, AIWG ships utilities for things the base platforms do not handle on their own: persistent artifact memory (`.aiwg/`), background orchestration (`aiwg mc`), autonomous loops (`aiwg ralph`), artifact indexing (`aiwg index`), cost telemetry, health diagnostics, and more. Most are opt-in. The deployment layer works standalone as plain text files the platform reads natively.
|
|
51
51
|
|
|
52
|
+
### Project scope (recommended) vs user scope (global)
|
|
53
|
+
|
|
54
|
+
`aiwg use` writes artifacts at one of two scopes. Both are first-class supported (see ADR-NUA-001 in `.aiwg/studies/novice-user-adoption/`):
|
|
55
|
+
|
|
56
|
+
- **Project scope** — default. Run `aiwg use sdlc` from a project root and the artifacts land in `./.claude/agents/`, `./.claude/skills/`, etc. One project's agent set never bleeds into another's session. **This is the recommended default for most use cases.**
|
|
57
|
+
- **User scope (global install)** — `aiwg use sdlc --scope user` writes to `~/.claude/agents/`, `~/.claude/skills/`, etc. Same artifact set loads into every session, regardless of project. Fits "AIWG in every conversation" workflows and is the canonical mode for OpenClaw and Hermes (whose primary discovery is user-scope).
|
|
58
|
+
|
|
59
|
+
The trade-off is real: when the same agent set loads into every session, context from one project can bleed into reasoning about another. Research (REF-720, *Lost in Multi-Turn Conversation*, MSR/Salesforce 2025) measured a 39% capability drop when this happens. The non-blocking project-isolation warning surfaces the trade-off at deploy time so the scope choice is informed. Neither scope is wrong; pick the one that fits the workflow.
|
|
60
|
+
|
|
61
|
+
See `docs/cli-reference.md` (under `aiwg use` → "Scope models") for the per-provider details and the global-install rough-edge inventory.
|
|
62
|
+
|
|
52
63
|
## Simple Building Blocks
|
|
53
64
|
|
|
54
65
|
AIWG ships five primitive artifact types. All are plain text:
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"consolidation": {
|
|
37
37
|
"strategy": "index-with-links",
|
|
38
38
|
"rulesIndex": "rules/RULES-INDEX.md",
|
|
39
|
-
"deployIndexOnly":
|
|
39
|
+
"deployIndexOnly": false,
|
|
40
|
+
"_note": "deployIndexOnly was true (#1343); changed to false 2026-05-14 so individual rule files (including skill-discovery.md) deploy to every provider's rules directory alongside the index. RULES-INDEX.md remains the canonical entry point; its links now resolve to local files on every provider, not just Claude/Cursor."
|
|
40
41
|
},
|
|
41
42
|
"commands": [],
|
|
42
43
|
"agents": [
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## Discover-First Protocol (CRITICAL)
|
|
2
|
+
|
|
3
|
+
Before declining a user request as "outside AIWG's scope" or improvising a workflow from training data, you MUST consult AIWG's artifact index.
|
|
4
|
+
|
|
5
|
+
AIWG ships ~400 skills, ~190 agents, ~70 commands, and ~60 rules across its installed frameworks. Most are NOT loaded into your context — they live at the install location and reach you via two CLI commands:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
aiwg discover "<paraphrased user need>"
|
|
9
|
+
aiwg show <type> <name> # e.g. aiwg show skill flow-deploy-to-production
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### When to run discover
|
|
13
|
+
|
|
14
|
+
You MUST run `aiwg discover` when any of the following is true:
|
|
15
|
+
|
|
16
|
+
- You are about to tell the user "AIWG doesn't have a way to do that"
|
|
17
|
+
- You are about to write a custom workflow / script / procedure from scratch
|
|
18
|
+
- The user's request mentions AIWG, a framework name (sdlc, research, forensics, ops, security-engineering, marketing, media-curator, knowledge-base), or capability keywords (skill, agent, command, rule, workflow, flow, template, addon)
|
|
19
|
+
- You are uncertain whether a curated AIWG artifact already addresses the request
|
|
20
|
+
|
|
21
|
+
You MAY skip discover only when:
|
|
22
|
+
|
|
23
|
+
- The user named a specific skill/command (e.g. `/flow-deploy-to-production`)
|
|
24
|
+
- The capability is clearly outside AIWG's scope (weather, translation, unrelated programming)
|
|
25
|
+
- You already ran `aiwg discover` for the same need this session
|
|
26
|
+
- A loaded quickref directly names the skill the user needs
|
|
27
|
+
|
|
28
|
+
### Discover-first, NOT filesystem-first
|
|
29
|
+
|
|
30
|
+
When the user's request mentions an AIWG keyword, `aiwg discover` is your FIRST tool call — not `Grep` / `Glob` / `Read` against provider directories like `.claude/`, `.codex/`, `.factory/`, `.warp/`, `.windsurf/`, `~/.openclaw/`, or `~/.hermes/`.
|
|
31
|
+
|
|
32
|
+
Filesystem search against those paths is FORBIDDEN as a first move for AIWG-internal lookups. The reason: the discover index covers 10x the surface area that any single provider directory holds, and gives you ranked results with capability summaries. Grep gives you a literal-string hit and stops.
|
|
33
|
+
|
|
34
|
+
### After discover returns a match
|
|
35
|
+
|
|
36
|
+
Use `aiwg show <type> <name>` to fetch the body — never `Read` on the returned path, never `find` / `ls` against the corpus directory. The CLI handles the read; you handle the application.
|
|
37
|
+
|
|
38
|
+
### Why this matters
|
|
39
|
+
|
|
40
|
+
Most AIWG skills are not in your context by design — that's how the kernel-skill model keeps your context lean while making the full corpus reachable. Enumerating from memory will miss obvious matches. The cost of running `aiwg discover` is one CLI invocation; the cost of skipping it is recommending a workflow AIWG already has, or declining a request AIWG can already serve.
|
|
41
|
+
|
|
42
|
+
The full discover-first rule (with detection heuristics, recovery procedure, and integration with other rules) is deployed at the framework's rules path as `skill-discovery.md`. Read it once at session start if you have not already.
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
"required": true,
|
|
16
16
|
"description": "Orchestration responsibilities, multi-agent workflow pattern, NL translations, available commands"
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"id": "discover-first",
|
|
20
|
+
"file": "02b-discover-first.md",
|
|
21
|
+
"title": "Discover-First Protocol",
|
|
22
|
+
"required": true,
|
|
23
|
+
"description": "Mandates `aiwg discover` + `aiwg show` before declining requests or improvising workflows. Closes hookup-matrix Finding #2 (#1344) — the discover-first protocol now reaches every provider that loads AIWG.md (Codex, Windsurf, OpenCode, Hermes, plus Warp via twin emission)."
|
|
24
|
+
},
|
|
18
25
|
{
|
|
19
26
|
"id": "aiwg-rules",
|
|
20
27
|
"file": "03-aiwg-rules.md",
|
|
@@ -8,6 +8,17 @@ This file provides project-specific instructions for GitHub Copilot.
|
|
|
8
8
|
|
|
9
9
|
This project uses the AIWG SDLC Framework for software development lifecycle management.
|
|
10
10
|
|
|
11
|
+
### Discover-First Protocol (CRITICAL)
|
|
12
|
+
|
|
13
|
+
Before declining a user request as "outside AIWG's scope" or improvising a workflow from training data, you MUST consult AIWG's artifact index. AIWG ships ~400 skills, ~190 agents, ~70 commands, and ~60 rules — most are NOT loaded into your context. Reach them via:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
aiwg discover "<paraphrased user need>"
|
|
17
|
+
aiwg show <type> <name> # e.g. aiwg show skill flow-deploy-to-production
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run `aiwg discover` whenever the user mentions AIWG, a framework name (sdlc, research, forensics, ops, marketing, security-engineering, media-curator, knowledge-base), or capability keywords (skill, agent, command, rule, workflow). Filesystem search against `.github/agents/`, `.github/skills/`, or `.github/instructions/` is FORBIDDEN as a first move for AIWG-internal lookups — `aiwg discover` covers 10x the surface area with ranked results. The full protocol is documented at `.github/copilot-rules/skill-discovery.md` (or `.github/instructions/skill-discovery.instructions.md`); read it once at session start.
|
|
21
|
+
|
|
11
22
|
### Available Custom Agents
|
|
12
23
|
|
|
13
24
|
AIWG agents are deployed to `.github/agents/` and can be invoked via `@agent-name`:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Security Engineering Rules Index
|
|
2
2
|
|
|
3
|
-
Applied-security enforcement rules for cryptographic primitive choices, chain-of-trust integrity, secret handling, and related design-time concerns. Deployed when the `security-engineering` framework is installed.
|
|
3
|
+
Applied-security enforcement rules for cryptographic primitive choices, chain-of-trust integrity, secret handling, supply-chain pinning, dependency-source policy, and related design-time concerns. Deployed when the `security-engineering` framework is installed.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## Tier 1 Rules (
|
|
7
|
+
## Tier 1 Rules (6 rules — applied cryptography + supply chain)
|
|
8
8
|
|
|
9
9
|
### HIGH
|
|
10
10
|
|
|
@@ -32,6 +32,18 @@ Applied-security enforcement rules for cryptographic primitive choices, chain-of
|
|
|
32
32
|
**Maps to review finding**: H6
|
|
33
33
|
**Full rule**: @$AIWG_ROOT/agentic/code/frameworks/security-engineering/rules/crypto-flag-verification.md
|
|
34
34
|
|
|
35
|
+
#### ci-action-pinning
|
|
36
|
+
**Summary**: Every CI workflow `uses:` reference MUST be a 40-character commit SHA (not a tag); every `container:`/`image:` reference MUST be `<name>:<tag>@sha256:<digest>`. Tools downloaded via `curl | sh` must record an observed-SHA log and support strict-mode SHA enforcement. Floating tags expose CI to silent supply-chain attacks (Shai-Hulud-class worm propagation). Maintain a pin manifest (`ci/digests.txt` or equivalent) as source of truth for diffs.
|
|
37
|
+
**When to apply**: Any workflow file under `.github/workflows/`, `.gitea/workflows/`, or equivalent; any tool-install step in CI
|
|
38
|
+
**Maps to issue**: #1293 (B3 / Mini Shai-Hulud)
|
|
39
|
+
**Full rule**: @$AIWG_ROOT/agentic/code/frameworks/security-engineering/rules/ci-action-pinning.md
|
|
40
|
+
|
|
41
|
+
#### dependency-source-policy
|
|
42
|
+
**Summary**: Non-registry dependency sources (`git+`, `github:`, raw tarball URLs, `file:`, `link:`) are PROHIBITED — they bypass registry signature verification and can execute arbitrary code at install time via `prepare` scripts (Mini Shai-Hulud's primary propagation vector). Policy applies to `package.json` AND transitive lockfile entries. Exceptions require an allowlist entry with owner, reason, review_date, and explicit risk acceptance. pnpm workspaces must set `blockExoticSubdeps: true` for workspace-scope enforcement.
|
|
43
|
+
**When to apply**: Any change to `package.json`, `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lockb`; CI lint should run on every push
|
|
44
|
+
**Maps to issue**: #1297 (Mini Shai-Hulud follow-up)
|
|
45
|
+
**Full rule**: @$AIWG_ROOT/agentic/code/frameworks/security-engineering/rules/dependency-source-policy.md
|
|
46
|
+
|
|
35
47
|
---
|
|
36
48
|
|
|
37
49
|
## Quick Reference by Context
|
|
@@ -43,7 +55,10 @@ Applied-security enforcement rules for cryptographic primitive choices, chain-of
|
|
|
43
55
|
| **Multi-key systems** | no-key-reuse-across-purposes, no-adhoc-kdf |
|
|
44
56
|
| **Password handling** | no-adhoc-kdf (Argon2id/PBKDF2 ≥600k) |
|
|
45
57
|
| **CLI crypto invocations** | crypto-flag-verification, no-unauthenticated-encryption |
|
|
46
|
-
| **
|
|
58
|
+
| **CI workflow review** | ci-action-pinning |
|
|
59
|
+
| **Container image references** | ci-action-pinning |
|
|
60
|
+
| **package.json / lockfile review** | dependency-source-policy |
|
|
61
|
+
| **Reviewing cryptographic decisions** | All four crypto rules in sequence |
|
|
47
62
|
|
|
48
63
|
---
|
|
49
64
|
|
|
@@ -53,5 +68,5 @@ Future Tier 2 rules will cover authentication-factor architecture, degraded-mode
|
|
|
53
68
|
|
|
54
69
|
---
|
|
55
70
|
|
|
56
|
-
*Generated from security-engineering framework —
|
|
71
|
+
*Generated from security-engineering framework — 6 rules in Tier 1*
|
|
57
72
|
*Full rule files: @$AIWG_ROOT/agentic/code/frameworks/security-engineering/rules/*
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# CI Action and Container Pinning
|
|
2
|
+
|
|
3
|
+
**Enforcement Level**: HIGH
|
|
4
|
+
**Scope**: All CI workflow files (`.github/workflows/`, `.gitea/workflows/`, equivalent) and any `container:` references therein
|
|
5
|
+
**Issue**: #1293
|
|
6
|
+
|
|
7
|
+
## Principle
|
|
8
|
+
|
|
9
|
+
CI workflows execute third-party code with broad permissions on every push. A workflow that references `actions/checkout@v4` or `node:24` resolves the reference at run time — the bytes that execute today are not guaranteed to be the bytes that execute tomorrow. Anyone who can move the `v4` tag (a GitHub Action maintainer compromise, a registry account takeover, a published-but-malicious update) silently runs new code in your CI. Pinning by immutable digest closes this attack surface.
|
|
10
|
+
|
|
11
|
+
This is the same standard AIWG applies to its own CI per [`dev-idempotent-builds.md`](.claude/rules/dev-idempotent-builds.md) rule 2 ("no `:latest` in production"). B3 extends the standard to user projects as a deployable rule.
|
|
12
|
+
|
|
13
|
+
## Mandatory Rules
|
|
14
|
+
|
|
15
|
+
### Rule 1: GitHub Actions references pinned by 40-char commit SHA
|
|
16
|
+
|
|
17
|
+
Every `uses:` reference in a workflow file MUST point at a 40-character commit SHA, not a tag or branch.
|
|
18
|
+
|
|
19
|
+
**FORBIDDEN**:
|
|
20
|
+
```yaml
|
|
21
|
+
- uses: actions/checkout@v4 # tag — can move
|
|
22
|
+
- uses: actions/checkout@main # branch — definitely moves
|
|
23
|
+
- uses: actions/checkout@v4.3.1 # version tag — usually immutable but a compromised account can re-point it
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**REQUIRED**:
|
|
27
|
+
```yaml
|
|
28
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The trailing comment is non-optional. It preserves human-readable diffability without compromising the immutability of the SHA pin.
|
|
32
|
+
|
|
33
|
+
### Rule 2: Container images pinned by sha256 digest
|
|
34
|
+
|
|
35
|
+
Every `container:` or `image:` reference in a workflow MUST be `<name>:<tag>@sha256:<digest>`.
|
|
36
|
+
|
|
37
|
+
**FORBIDDEN**:
|
|
38
|
+
```yaml
|
|
39
|
+
container: node:24
|
|
40
|
+
container: node:latest
|
|
41
|
+
container: node:24-bookworm
|
|
42
|
+
container: ghcr.io/owner/image:v2.1.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**REQUIRED**:
|
|
46
|
+
```yaml
|
|
47
|
+
container: node:24@sha256:050bf2bbe33c1d6754e060bec89378a79ed831f04a7bb1a53fe45e997df7b3bb # 24.15.0
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `<name>:<tag>` prefix is retained for human readability; the digest is the trust anchor. Trailing comment captures the resolved version for diff context.
|
|
51
|
+
|
|
52
|
+
### Rule 3: Pin manifest at `ci/digests.txt` (or equivalent)
|
|
53
|
+
|
|
54
|
+
Every project that enforces this rule MUST maintain a pin manifest listing each pinned reference, its resolved version, the date pinned, and the rationale. The manifest is the source of truth for diffs — a SHA change without a corresponding manifest entry is a red flag.
|
|
55
|
+
|
|
56
|
+
Reference manifest format: see AIWG's own [`ci/digests.txt`](https://git.integrolabs.net/roctinam/aiwg/src/branch/main/ci/digests.txt).
|
|
57
|
+
|
|
58
|
+
Minimum row format:
|
|
59
|
+
```
|
|
60
|
+
<kind> <name> <pin> <resolved-version> <date-pinned> <update-rationale>
|
|
61
|
+
container node:24 sha256:050bf2bbe33c1d6754e060bec89378a79ed831f04a7bb1a53fe45e997df7b3bb 24.15.0 2026-05-12 initial pin
|
|
62
|
+
action actions/checkout 93cb6efe18208431cddfb8368fd83d5badbf9bfd v5.0.1 2026-05-12 initial pin
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Rule 4: Pin bumps go through review
|
|
66
|
+
|
|
67
|
+
Every pin update is a reviewable change. The commit should:
|
|
68
|
+
- Reference the issue or advisory motivating the bump (CVE, needed feature, scheduled rotation)
|
|
69
|
+
- Update the manifest row in the same commit as the workflow file edit
|
|
70
|
+
- Include `chore(ci): bump <name> pin to <version> (refs #<issue>)` style commit message
|
|
71
|
+
|
|
72
|
+
Workflow diffs that change a digest without a corresponding manifest update fail review.
|
|
73
|
+
|
|
74
|
+
### Rule 5: Standalone tools pinned by version + checksum
|
|
75
|
+
|
|
76
|
+
Tools downloaded by `curl | sh` or equivalent in CI (e.g., `syft install.sh`, ad-hoc binary releases) MUST pin a specific version AND record the installer's content hash. The version pin gives reproducibility; the content hash detects upstream tampering.
|
|
77
|
+
|
|
78
|
+
**Acceptable pattern** (strict-mode opt-in, with observed-SHA logging):
|
|
79
|
+
```yaml
|
|
80
|
+
- name: Install syft
|
|
81
|
+
run: |
|
|
82
|
+
VERSION=v1.18.0
|
|
83
|
+
EXPECTED_INSTALL_SHA="" # populate after first run; logs `observed` SHA
|
|
84
|
+
SCRIPT=$(curl -fsSL "https://raw.githubusercontent.com/anchore/syft/${VERSION}/install.sh")
|
|
85
|
+
OBSERVED_SHA=$(printf '%s' "$SCRIPT" | sha256sum | awk '{print $1}')
|
|
86
|
+
echo "observed install-script SHA: ${OBSERVED_SHA}"
|
|
87
|
+
if [ -n "$EXPECTED_INSTALL_SHA" ] && [ "$OBSERVED_SHA" != "$EXPECTED_INSTALL_SHA" ]; then
|
|
88
|
+
echo "✗ install-script SHA drift — refusing to execute"
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
printf '%s' "$SCRIPT" | sh -s -- -b /usr/local/bin "$VERSION"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Detection Patterns
|
|
95
|
+
|
|
96
|
+
CI lint should flag the following:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Floating action tags
|
|
100
|
+
grep -rE '^\s*-\s*uses:\s*[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+@(v?[0-9]+(\.[0-9]+)*|main|master|latest)\s*(#.*)?$' \
|
|
101
|
+
.github/workflows/ .gitea/workflows/ 2>/dev/null
|
|
102
|
+
|
|
103
|
+
# Unpinned container images
|
|
104
|
+
grep -rE '^\s*(container|image):\s*[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+\s*(#.*)?$' \
|
|
105
|
+
.github/workflows/ .gitea/workflows/ 2>/dev/null \
|
|
106
|
+
| grep -v 'sha256:'
|
|
107
|
+
|
|
108
|
+
# Bare :latest anywhere
|
|
109
|
+
grep -rnE ':latest\b' .github/workflows/ .gitea/workflows/ 2>/dev/null
|
|
110
|
+
|
|
111
|
+
# Curl-pipe-shell installers without a content hash check
|
|
112
|
+
grep -rnE 'curl[^|]+\|\s*(bash|sh)' .github/workflows/ .gitea/workflows/ 2>/dev/null
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Wire these into a `npm run lint:ci-pins` script (or equivalent) and call it from the CI workflow itself so the rule is self-enforcing.
|
|
116
|
+
|
|
117
|
+
## Acceptable Exceptions
|
|
118
|
+
|
|
119
|
+
The following are NOT subject to this rule:
|
|
120
|
+
|
|
121
|
+
1. **Dockerfile `FROM` instructions in build stages** that are NOT consumed by CI directly — those are governed by the project's image-build pipeline (which should follow the same standard, but the rule lives in the Dockerfile review, not the workflow review).
|
|
122
|
+
2. **Ephemeral self-hosted runners** that pull pre-baked AMIs/images with their own attestation chain — the chain-of-trust is at the AMI level, not the workflow level.
|
|
123
|
+
3. **Reusable workflows from the same repository** (`uses: ./.github/workflows/foo.yml`) — these are pinned to the repository state at the calling commit.
|
|
124
|
+
4. **Build-arg substitutions for the same digest** — `FROM ${BASE_IMAGE}` where `BASE_IMAGE` is built from a digest-pinned reference one level up.
|
|
125
|
+
|
|
126
|
+
Document any other exception in a code comment adjacent to the unpinned reference, with reasoning and an issue link tracking eventual migration to a pin.
|
|
127
|
+
|
|
128
|
+
## Rationale
|
|
129
|
+
|
|
130
|
+
The mutable-tag attack surface is one of the largest unaddressed components of npm-ecosystem supply-chain risk as of 2026. The recent Shai-Hulud worm campaign demonstrated that compromised maintainer accounts can push malicious updates that propagate via floating version tags within hours. Workflows pinned by SHA stop that propagation at the boundary of every individual project that pins.
|
|
131
|
+
|
|
132
|
+
Pin-bump cost is low (one curl/git ls-remote command); incident cost from an unpinned dependency executing in CI with secret access is high. The asymmetry justifies the rule.
|
|
133
|
+
|
|
134
|
+
## References
|
|
135
|
+
|
|
136
|
+
- [`dev-idempotent-builds.md`](.claude/rules/dev-idempotent-builds.md) — base rule on reproducible builds
|
|
137
|
+
- AIWG's own pin manifest: `ci/digests.txt`
|
|
138
|
+
- Defenses brief C7 (action pinning) and C8 (container pinning)
|
|
139
|
+
- Shai-Hulud worm post-mortem references
|
|
140
|
+
- GitHub Actions documentation: pinning third-party actions to a full-length commit SHA
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
**Rule Status**: ACTIVE
|
|
145
|
+
**Last Updated**: 2026-05-13
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Dependency Source Policy
|
|
2
|
+
|
|
3
|
+
**Enforcement Level**: HIGH
|
|
4
|
+
**Scope**: `package.json` and lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`) — direct AND transitive entries
|
|
5
|
+
**Issue**: #1297
|
|
6
|
+
|
|
7
|
+
## Principle
|
|
8
|
+
|
|
9
|
+
Non-registry dependency sources (`git+`, `github:`, raw tarball URLs, `file:`, `link:`) bypass registry signature verification AND can execute arbitrary code during install via lifecycle scripts (especially `prepare`). The May 2026 Mini Shai-Hulud campaign's primary propagation vector was an `optionalDependencies` entry sourced from `git+https://...attacker-repo` whose `prepare` script ran during every `npm install` of any project that pulled in the affected package.
|
|
10
|
+
|
|
11
|
+
Registry-hosted tarballs (`registry.npmjs.org/.../foo.tgz`, `registry.yarnpkg.com/...`, `npm.pkg.github.com/...`) are fine — they're the normal `resolved` URL format `npm install` emits for every registry-published dep. The policy only flags non-registry sources.
|
|
12
|
+
|
|
13
|
+
Companion to [`ci-action-pinning`](ci-action-pinning.md) (CI execution-environment trust) and the per-package-manager release-age-gate skills (which gate freshly-published registry versions). Together they cover the three primary supply-chain attack vectors: CI environment poisoning, registry-version poisoning, and direct dep-source injection.
|
|
14
|
+
|
|
15
|
+
## Mandatory Rules
|
|
16
|
+
|
|
17
|
+
### Rule 1: Six dep-source patterns are forbidden by default
|
|
18
|
+
|
|
19
|
+
Every dep in `package.json` AND every entry in the lockfile (including transitive) MUST be a registry-published version reference. The following patterns are blocked:
|
|
20
|
+
|
|
21
|
+
| Pattern | Example | Reason |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `git+*` scheme | `"foo": "git+https://github.com/owner/foo.git"` | npm clones the repo and runs its `prepare` script — arbitrary code execution at install time |
|
|
24
|
+
| `git://` scheme | `"foo": "git://github.com/owner/foo.git"` | Same as above |
|
|
25
|
+
| `github:` shorthand | `"foo": "github:owner/foo"` | Same as above; npm expands to git+ |
|
|
26
|
+
| Non-registry tarball | `"foo": "https://example.com/foo-1.0.0.tgz"` | Tarball can contain any payload and lifecycle scripts; bypasses registry signature verification |
|
|
27
|
+
| `file:` path | `"foo": "file:./vendor/foo"` | Local-path deps bypass dep-resolution review and lockfile signature checks |
|
|
28
|
+
| `link:` symlink | `"foo": "link:./packages/foo"` | Follows the symlink target wherever it points; same gaps as `file:` |
|
|
29
|
+
|
|
30
|
+
Registry-hosted patterns that are ALWAYS acceptable:
|
|
31
|
+
|
|
32
|
+
- `"foo": "^1.2.3"` — registry semver range
|
|
33
|
+
- `"foo": "1.2.3"` — exact registry version
|
|
34
|
+
- `"foo": "npm:@scope/foo@^1.2.3"` — aliased registry dep
|
|
35
|
+
- Lockfile `"resolved": "https://registry.npmjs.org/..."` — registry-served tarball
|
|
36
|
+
|
|
37
|
+
### Rule 2: Exceptions require an allowlist entry
|
|
38
|
+
|
|
39
|
+
If a legitimate non-registry source exists (e.g., temporary fork pending upstream merge, monorepo-internal `file:` link with a known security boundary), it MUST be allowlisted with:
|
|
40
|
+
|
|
41
|
+
- **Owner** — who took the risk-acceptance decision
|
|
42
|
+
- **Reason** — why a registry version isn't viable today
|
|
43
|
+
- **Expiry / review date** — when this exception must be re-evaluated (max 1 year)
|
|
44
|
+
- **Risk acceptance** — explicit statement that the operator accepts the dep-source bypass
|
|
45
|
+
|
|
46
|
+
Allowlist file format (committed at `.aiwg/security/dep-source-allowlist.yaml` or equivalent):
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
allowlist:
|
|
50
|
+
- dep: "foo"
|
|
51
|
+
source: "git+https://github.com/owner/foo.git#sha-or-tag"
|
|
52
|
+
owner: "alice@example.org"
|
|
53
|
+
reason: "Upstream PR #123 not yet merged; we need fix from main for issue X"
|
|
54
|
+
review_date: "2026-08-01"
|
|
55
|
+
risk_acceptance: "Source pinned to commit SHA; reviewed prepare script line-by-line; no shell exec in install path"
|
|
56
|
+
- dep: "@internal/shared"
|
|
57
|
+
source: "file:./packages/shared"
|
|
58
|
+
owner: "bob@example.org"
|
|
59
|
+
reason: "Monorepo-internal package; not published to registry"
|
|
60
|
+
review_date: "2027-01-01"
|
|
61
|
+
risk_acceptance: "Source within same security boundary as host package; no external code path"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Allowlist entries beyond their `review_date` are treated as policy violations.
|
|
65
|
+
|
|
66
|
+
### Rule 3: Lockfile entries are policy targets
|
|
67
|
+
|
|
68
|
+
Direct deps in `package.json` are easy to spot. Transitive entries in the lockfile are the actual attack surface — Mini Shai-Hulud propagated via transitive `optionalDependencies` with exotic sources. The CI lint MUST scan the lockfile for the same patterns, including:
|
|
69
|
+
|
|
70
|
+
- `"resolved": "git+..."` in `package-lock.json`
|
|
71
|
+
- `tarball: ...` URLs in `pnpm-lock.yaml` that don't match a registry origin
|
|
72
|
+
- `resolution: ` blocks in `yarn.lock`
|
|
73
|
+
- Binary lockfile (`bun.lockb`) — extract via `bun pm ls --all` and pattern-match the output
|
|
74
|
+
|
|
75
|
+
### Rule 4: pnpm workspaces enable `blockExoticSubdeps`
|
|
76
|
+
|
|
77
|
+
When pnpm is the package manager, the workspace MUST set `blockExoticSubdeps: true` (in `pnpm-workspace.yaml`) or the per-repo equivalent in `.npmrc`:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
# pnpm-workspace.yaml
|
|
81
|
+
packages:
|
|
82
|
+
- 'packages/*'
|
|
83
|
+
blockExoticSubdeps: true
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This is the pnpm advantage over npm: workspace-scope enforcement that npm has no equivalent for. The `pnpm-release-age-gate` skill documents the wiring.
|
|
87
|
+
|
|
88
|
+
### Rule 5: Failure messages point to remediation
|
|
89
|
+
|
|
90
|
+
When CI rejects a violation, the failure message MUST:
|
|
91
|
+
|
|
92
|
+
1. State which dep and which source pattern matched
|
|
93
|
+
2. Reference the operator's choices: switch to a registry version, request an allowlist entry, or accept install-time compromise risk
|
|
94
|
+
3. Link to this rule (or the project's adaptation of it) so the receiving developer can read the policy
|
|
95
|
+
|
|
96
|
+
Reference failure-message format (from AIWG's own `tools/lint/dep-source.mjs`):
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
✗ Dependency source policy violation
|
|
100
|
+
|
|
101
|
+
foo (in package.json)
|
|
102
|
+
source: git+https://github.com/owner/foo.git
|
|
103
|
+
pattern: git+ scheme
|
|
104
|
+
|
|
105
|
+
This dependency source bypasses registry signature verification and
|
|
106
|
+
executes arbitrary code at install time via the prepare script.
|
|
107
|
+
|
|
108
|
+
Options:
|
|
109
|
+
1. Switch to a registry-published version: npm install foo@<version>
|
|
110
|
+
2. Add an allowlist entry: see .aiwg/security/dep-source-allowlist.yaml
|
|
111
|
+
3. Read the policy: docs/contributing/dependency-sources.md
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Detection Patterns
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# package.json — direct deps with exotic sources
|
|
118
|
+
node -p "
|
|
119
|
+
const p = require('./package.json');
|
|
120
|
+
const all = {...p.dependencies, ...p.devDependencies, ...p.optionalDependencies, ...p.peerDependencies};
|
|
121
|
+
for (const [name, source] of Object.entries(all || {})) {
|
|
122
|
+
if (/^(git\+|git:|github:|file:|link:)/.test(source) ||
|
|
123
|
+
(/^https?:\/\//.test(source) && /\.tgz/.test(source))) {
|
|
124
|
+
console.log(name, '=', source);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
"
|
|
128
|
+
|
|
129
|
+
# package-lock.json — transitive entries with exotic resolved URLs
|
|
130
|
+
node -p "
|
|
131
|
+
const lock = require('./package-lock.json');
|
|
132
|
+
function walk(pkgs, prefix = '') {
|
|
133
|
+
for (const [path, entry] of Object.entries(pkgs || {})) {
|
|
134
|
+
if (entry.resolved && /^(git\+|git:)/.test(entry.resolved)) {
|
|
135
|
+
console.log(prefix + path, '=', entry.resolved);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
walk(lock.packages);
|
|
140
|
+
"
|
|
141
|
+
|
|
142
|
+
# pnpm-lock.yaml — exotic resolution blocks
|
|
143
|
+
grep -E 'resolution:\s*\{(git|tarball:.*[^.]https?://[^/]+/[^.]*\.tgz)' pnpm-lock.yaml 2>/dev/null
|
|
144
|
+
|
|
145
|
+
# yarn.lock — exotic resolved entries
|
|
146
|
+
grep -E '"?resolved"?\s*"?:?\s*"?(git\+|git:)' yarn.lock 2>/dev/null
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Reference implementation: AIWG's own [`tools/lint/dep-source.mjs`](https://git.integrolabs.net/roctinam/aiwg/src/branch/main/tools/lint/dep-source.mjs) — runs `npm run lint:dep-sources` and exits non-zero on any violation.
|
|
150
|
+
|
|
151
|
+
## Acceptable Exceptions
|
|
152
|
+
|
|
153
|
+
Three exception categories beyond explicit allowlist entries:
|
|
154
|
+
|
|
155
|
+
1. **Monorepo workspace links** — `file:` or `link:` deps between packages in the same `pnpm-workspace.yaml` / `lerna.json` / `nx.json` / `package.json workspaces` configuration. These are within the same security boundary; flag as INFO only.
|
|
156
|
+
2. **Lockfile resolved-URL prefixes that look exotic but ARE registry URLs** — e.g., `https://registry.npmjs.org/...` matches `^https://` but is a registry origin. The detection logic above already excludes these correctly by checking the host.
|
|
157
|
+
3. **Dev-only deps for build tooling that consume only `package.json`-declared scripts** — debatable; recommend treating as standard policy violations and using allowlist entries for the temporary cases.
|
|
158
|
+
|
|
159
|
+
## Rationale
|
|
160
|
+
|
|
161
|
+
Mini Shai-Hulud demonstrated that a single transitive `optionalDependencies` entry from a compromised maintainer can propagate a `prepare`-script payload to every downstream `npm install`. The registry's "this version was published by an authenticated maintainer" guarantee is the foundation of `npm audit signatures`; non-registry sources bypass that guarantee entirely.
|
|
162
|
+
|
|
163
|
+
Cost of compliance is low (most deps are already registry-published). Cost of a single exotic-source incident is high (full secret-rotation cycle, downstream user trust loss, potential CVE assignment). The asymmetry justifies the rule.
|
|
164
|
+
|
|
165
|
+
## References
|
|
166
|
+
|
|
167
|
+
- [`ci-action-pinning`](ci-action-pinning.md) — CI execution-environment trust (companion rule)
|
|
168
|
+
- AIWG's lint implementation: [`tools/lint/dep-source.mjs`](https://git.integrolabs.net/roctinam/aiwg/src/branch/main/tools/lint/dep-source.mjs)
|
|
169
|
+
- AIWG's contributor doc: [`docs/contributing/dependency-sources.md`](https://git.integrolabs.net/roctinam/aiwg/src/branch/main/docs/contributing/dependency-sources.md)
|
|
170
|
+
- ADR: `.aiwg/architecture/adr-dep-source-policy.md` (in AIWG's own repo)
|
|
171
|
+
- pnpm `blockExoticSubdeps`: <https://pnpm.io/settings#blockexoticsubdeps>
|
|
172
|
+
- Mini Shai-Hulud post-mortem references
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
**Rule Status**: ACTIVE
|
|
177
|
+
**Last Updated**: 2026-05-13
|