Skip to content

Commit ba2ba49

Browse files
authored
fix: security hardening across CLI (#104)
* fix: security hardening across CLI - Sandbox RuleContext file operations to project root (path traversal + symlink protection) - Set credentials file permissions to 0600 - URL-encode credentials in marketplace URLs - Validate tar archive entries before extraction (plugin-install + binary-upgrade) - Disable HTTP redirects on security-sensitive fetch endpoints - Add SHA256 checksum generation to release workflow and verification on download - Add PII consent prompt before signup - Wrap JSON.parse in try-catch for corrupted settings files - Use path.join() instead of template string concatenation in adr/list - Add security documentation page (EN + PT-BR) * docs: regenerate llms-full.txt * docs: update security page with symlink protection and upgrade integrity * docs: regenerate llms-full.txt * test: add integration tests for check command security sandbox 10 tests covering the full loadRuleAdrs → runChecks pipeline with on-disk .rules.ts files attempting path traversal, absolute paths, glob escapes, symlink following, and legitimate access.
1 parent cb11d6d commit ba2ba49

15 files changed

Lines changed: 1056 additions & 26 deletions

File tree

.github/workflows/release-binaries.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ jobs:
8787
cp dist/${{ matrix.artifact }} archgate
8888
tar -czf ${{ matrix.artifact }}.tar.gz archgate
8989
rm archgate
90+
shasum -a 256 ${{ matrix.artifact }}.tar.gz > ${{ matrix.artifact }}.tar.gz.sha256
9091
9192
- name: Prepare release asset (Windows)
9293
if: runner.os == 'Windows'
@@ -95,14 +96,15 @@ jobs:
9596
Copy-Item "dist/${{ matrix.artifact }}.exe" "archgate.exe"
9697
Compress-Archive -Path "archgate.exe" -DestinationPath "${{ matrix.artifact }}.zip"
9798
Remove-Item "archgate.exe"
99+
(Get-FileHash "${{ matrix.artifact }}.zip" -Algorithm SHA256).Hash.ToLower() + " ${{ matrix.artifact }}.zip" | Out-File -Encoding ascii "${{ matrix.artifact }}.zip.sha256"
98100
99101
- name: Upload release asset (Unix)
100102
if: runner.os != 'Windows'
101103
env:
102104
GH_TOKEN: ${{ github.token }}
103105
run: |
104106
TAG="${{ github.event.release.tag_name || inputs.tag }}"
105-
gh release upload "$TAG" "${{ matrix.artifact }}.tar.gz" --clobber
107+
gh release upload "$TAG" "${{ matrix.artifact }}.tar.gz" "${{ matrix.artifact }}.tar.gz.sha256" --clobber
106108
107109
- name: Upload release asset (Windows)
108110
if: runner.os == 'Windows'
@@ -111,4 +113,4 @@ jobs:
111113
GH_TOKEN: ${{ github.token }}
112114
run: |
113115
$tag = "${{ github.event.release.tag_name || inputs.tag }}"
114-
gh release upload $tag "${{ matrix.artifact }}.zip" --clobber
116+
gh release upload $tag "${{ matrix.artifact }}.zip" "${{ matrix.artifact }}.zip.sha256" --clobber

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export default defineConfig({
219219
{ label: "Copilot CLI Plugin", slug: "guides/copilot-cli-plugin" },
220220
{ label: "Cursor Integration", slug: "guides/cursor-integration" },
221221
{ label: "Pre-commit Hooks", slug: "guides/pre-commit-hooks" },
222+
{ label: "Security", slug: "guides/security" },
222223
],
223224
},
224225
{

docs/public/llms-full.txt

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,148 @@ Each command runs independently. If any command exits with a non-zero code, the
16761676

16771677
---
16781678

1679+
## Guides: Security
1680+
1681+
Source: https://cli.archgate.dev/guides/security/
1682+
1683+
Archgate executes TypeScript rules from `.rules.ts` files in your repository. This page explains the trust model, what rules can and cannot do, and how to run checks safely.
1684+
1685+
## Trust model
1686+
1687+
**`.rules.ts` files are executable code.** When you run `archgate check`, the CLI dynamically imports every `.rules.ts` companion file and runs its `check` functions. This is equivalent to running `bun .archgate/adrs/*.rules.ts` -- the code has the same capabilities as any other script on your machine.
1688+
1689+
This means:
1690+
1691+
- Only run `archgate check` on repositories you trust.
1692+
- Review `.rules.ts` files with the same scrutiny as any other source code in the project.
1693+
- In open-source projects, treat `.rules.ts` changes in pull requests as security-sensitive.
1694+
1695+
### What rules can access
1696+
1697+
Rules receive a `RuleContext` object with sandboxed file operations. All `RuleContext` methods (`readFile`, `readJSON`, `grep`, `grepFiles`, `glob`) are restricted to the project root directory -- path traversal via `../`, absolute paths, and symbolic links are blocked and throw an error.
1698+
1699+
However, because rules are standard TypeScript modules, they can also use any Bun/Node.js API directly:
1700+
1701+
| Capability | Via RuleContext | Via direct API calls |
1702+
| -------------------------- | --------------- | ------------------------ |
1703+
| Read files in project | Yes (sandboxed) | Yes (unrestricted) |
1704+
| Read files outside project | Blocked | Yes (`Bun.file()`, `fs`) |
1705+
| Network requests | No API provided | Yes (`fetch()`) |
1706+
| Spawn subprocesses | No API provided | Yes (`Bun.spawn()`) |
1707+
| Environment variables | No API provided | Yes (`process.env`) |
1708+
1709+
The `RuleContext` sandbox prevents accidental path traversal in well-intentioned rules. It does not protect against deliberately malicious code -- a rule that imports `fs` directly can bypass the sandbox.
1710+
1711+
### What rules cannot do
1712+
1713+
- **Write files** -- the `RuleContext` API is read-only. Rules report violations but cannot modify the codebase.
1714+
- **Escape the 30-second timeout** -- each rule is killed after 30 seconds of wall-clock time.
1715+
- **Affect other rules** -- rules from different ADRs run in parallel but share no mutable state through the context API.
1716+
1717+
## CI/CD best practices
1718+
1719+
Running `archgate check` in CI is safe when you control the repository content. Extra care is needed for pull requests from external contributors.
1720+
1721+
### Trusted branches
1722+
1723+
For pushes to `main` or other protected branches, `archgate check` runs code that has already been reviewed and merged. This is safe:
1724+
1725+
```yaml
1726+
on:
1727+
push:
1728+
branches: [main]
1729+
1730+
jobs:
1731+
check:
1732+
runs-on: ubuntu-latest
1733+
steps:
1734+
- uses: actions/checkout@v4
1735+
- uses: archgate/check-action@v1
1736+
```
1737+
1738+
### Pull requests from forks
1739+
1740+
When a pull request comes from a fork, the `.rules.ts` files in the PR may contain arbitrary code. This is the same risk as running any untrusted CI script.
1741+
1742+
**Option 1: Require approval before running.** Use GitHub's environment protection rules or `pull_request_target` with manual approval to gate CI on review:
1743+
1744+
```yaml
1745+
on:
1746+
pull_request_target:
1747+
1748+
jobs:
1749+
check:
1750+
runs-on: ubuntu-latest
1751+
environment: pr-check # Requires manual approval
1752+
steps:
1753+
- uses: actions/checkout@v4
1754+
with:
1755+
ref: ${{ github.event.pull_request.head.sha }}
1756+
- uses: archgate/check-action@v1
1757+
```
1758+
1759+
**Option 2: Only run checks on trusted files.** Use a separate workflow that checks out the base branch's `.rules.ts` files and runs them against the PR's source files. This ensures only reviewed rules execute.
1760+
1761+
**Option 3: Skip checks on fork PRs.** If your rules are primarily for internal governance, skip automated checks on fork PRs and run them manually after review:
1762+
1763+
```yaml
1764+
on:
1765+
pull_request:
1766+
1767+
jobs:
1768+
check:
1769+
if: github.event.pull_request.head.repo.full_name == github.repository
1770+
runs-on: ubuntu-latest
1771+
steps:
1772+
- uses: actions/checkout@v4
1773+
- uses: archgate/check-action@v1
1774+
```
1775+
1776+
### Least-privilege runners
1777+
1778+
Run `archgate check` on runners with minimal permissions. The job only needs read access to the repository -- no secrets, deployment keys, or write permissions are required:
1779+
1780+
```yaml
1781+
jobs:
1782+
check:
1783+
runs-on: ubuntu-latest
1784+
permissions:
1785+
contents: read
1786+
steps:
1787+
- uses: actions/checkout@v4
1788+
- uses: archgate/check-action@v1
1789+
```
1790+
1791+
## Local development
1792+
1793+
### Reviewing rules in new repositories
1794+
1795+
When cloning or forking a repository that uses Archgate, review the `.archgate/adrs/*.rules.ts` files before running `archgate check` for the first time. Look for:
1796+
1797+
- Imports of `fs`, `child_process`, `net`, or other system modules
1798+
- Calls to `fetch()`, `Bun.spawn()`, or `Bun.file()` outside the `RuleContext` API
1799+
- Top-level code that runs on import (before the `check` function is called)
1800+
1801+
Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output.
1802+
1803+
### Credentials
1804+
1805+
The `archgate login` command stores an authentication token at `~/.archgate/credentials` with owner-only file permissions (`0600` on Unix). This token is used for plugin installation and is never sent to third parties beyond the Archgate plugins service.
1806+
1807+
- Do not commit `~/.archgate/credentials` to version control.
1808+
- Do not share the token in CI logs. Plugin installation commands pass credentials via authenticated URLs to git, which may appear in process listings. Avoid running `archgate plugin install` with verbose logging in shared CI environments.
1809+
- To revoke access, run `archgate logout` or delete `~/.archgate/credentials`.
1810+
1811+
### Self-update integrity
1812+
1813+
When you run `archgate upgrade`, the CLI downloads the release binary from GitHub Releases and verifies its SHA256 checksum before extraction. If the checksum does not match, the upgrade is aborted. This protects against tampered downloads due to network interception or compromised mirrors.
1814+
1815+
## Reporting vulnerabilities
1816+
1817+
If you discover a security issue in Archgate, please report it responsibly by opening a [GitHub issue](https://github.com/archgate/cli/issues) or contacting the maintainers directly. Do not include exploit code in public issues.
1818+
1819+
---
1820+
16791821
## Guides: VS Code Plugin
16801822

16811823
Source: https://cli.archgate.dev/guides/vscode-plugin/
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
title: Security
3+
description: Understand the Archgate CLI trust model, how rules are executed, and best practices for running checks safely in CI/CD and local development.
4+
---
5+
6+
Archgate executes TypeScript rules from `.rules.ts` files in your repository. This page explains the trust model, what rules can and cannot do, and how to run checks safely.
7+
8+
## Trust model
9+
10+
**`.rules.ts` files are executable code.** When you run `archgate check`, the CLI dynamically imports every `.rules.ts` companion file and runs its `check` functions. This is equivalent to running `bun .archgate/adrs/*.rules.ts` -- the code has the same capabilities as any other script on your machine.
11+
12+
This means:
13+
14+
- Only run `archgate check` on repositories you trust.
15+
- Review `.rules.ts` files with the same scrutiny as any other source code in the project.
16+
- In open-source projects, treat `.rules.ts` changes in pull requests as security-sensitive.
17+
18+
### What rules can access
19+
20+
Rules receive a `RuleContext` object with sandboxed file operations. All `RuleContext` methods (`readFile`, `readJSON`, `grep`, `grepFiles`, `glob`) are restricted to the project root directory -- path traversal via `../`, absolute paths, and symbolic links are blocked and throw an error.
21+
22+
However, because rules are standard TypeScript modules, they can also use any Bun/Node.js API directly:
23+
24+
| Capability | Via RuleContext | Via direct API calls |
25+
| -------------------------- | --------------- | ------------------------ |
26+
| Read files in project | Yes (sandboxed) | Yes (unrestricted) |
27+
| Read files outside project | Blocked | Yes (`Bun.file()`, `fs`) |
28+
| Network requests | No API provided | Yes (`fetch()`) |
29+
| Spawn subprocesses | No API provided | Yes (`Bun.spawn()`) |
30+
| Environment variables | No API provided | Yes (`process.env`) |
31+
32+
The `RuleContext` sandbox prevents accidental path traversal in well-intentioned rules. It does not protect against deliberately malicious code -- a rule that imports `fs` directly can bypass the sandbox.
33+
34+
### What rules cannot do
35+
36+
- **Write files** -- the `RuleContext` API is read-only. Rules report violations but cannot modify the codebase.
37+
- **Escape the 30-second timeout** -- each rule is killed after 30 seconds of wall-clock time.
38+
- **Affect other rules** -- rules from different ADRs run in parallel but share no mutable state through the context API.
39+
40+
## CI/CD best practices
41+
42+
Running `archgate check` in CI is safe when you control the repository content. Extra care is needed for pull requests from external contributors.
43+
44+
### Trusted branches
45+
46+
For pushes to `main` or other protected branches, `archgate check` runs code that has already been reviewed and merged. This is safe:
47+
48+
```yaml
49+
on:
50+
push:
51+
branches: [main]
52+
53+
jobs:
54+
check:
55+
runs-on: ubuntu-latest
56+
steps:
57+
- uses: actions/checkout@v4
58+
- uses: archgate/check-action@v1
59+
```
60+
61+
### Pull requests from forks
62+
63+
When a pull request comes from a fork, the `.rules.ts` files in the PR may contain arbitrary code. This is the same risk as running any untrusted CI script.
64+
65+
**Option 1: Require approval before running.** Use GitHub's environment protection rules or `pull_request_target` with manual approval to gate CI on review:
66+
67+
```yaml
68+
on:
69+
pull_request_target:
70+
71+
jobs:
72+
check:
73+
runs-on: ubuntu-latest
74+
environment: pr-check # Requires manual approval
75+
steps:
76+
- uses: actions/checkout@v4
77+
with:
78+
ref: ${{ github.event.pull_request.head.sha }}
79+
- uses: archgate/check-action@v1
80+
```
81+
82+
**Option 2: Only run checks on trusted files.** Use a separate workflow that checks out the base branch's `.rules.ts` files and runs them against the PR's source files. This ensures only reviewed rules execute.
83+
84+
**Option 3: Skip checks on fork PRs.** If your rules are primarily for internal governance, skip automated checks on fork PRs and run them manually after review:
85+
86+
```yaml
87+
on:
88+
pull_request:
89+
90+
jobs:
91+
check:
92+
if: github.event.pull_request.head.repo.full_name == github.repository
93+
runs-on: ubuntu-latest
94+
steps:
95+
- uses: actions/checkout@v4
96+
- uses: archgate/check-action@v1
97+
```
98+
99+
### Least-privilege runners
100+
101+
Run `archgate check` on runners with minimal permissions. The job only needs read access to the repository -- no secrets, deployment keys, or write permissions are required:
102+
103+
```yaml
104+
jobs:
105+
check:
106+
runs-on: ubuntu-latest
107+
permissions:
108+
contents: read
109+
steps:
110+
- uses: actions/checkout@v4
111+
- uses: archgate/check-action@v1
112+
```
113+
114+
## Local development
115+
116+
### Reviewing rules in new repositories
117+
118+
When cloning or forking a repository that uses Archgate, review the `.archgate/adrs/*.rules.ts` files before running `archgate check` for the first time. Look for:
119+
120+
- Imports of `fs`, `child_process`, `net`, or other system modules
121+
- Calls to `fetch()`, `Bun.spawn()`, or `Bun.file()` outside the `RuleContext` API
122+
- Top-level code that runs on import (before the `check` function is called)
123+
124+
Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output.
125+
126+
### Credentials
127+
128+
The `archgate login` command stores an authentication token at `~/.archgate/credentials` with owner-only file permissions (`0600` on Unix). This token is used for plugin installation and is never sent to third parties beyond the Archgate plugins service.
129+
130+
- Do not commit `~/.archgate/credentials` to version control.
131+
- Do not share the token in CI logs. Plugin installation commands pass credentials via authenticated URLs to git, which may appear in process listings. Avoid running `archgate plugin install` with verbose logging in shared CI environments.
132+
- To revoke access, run `archgate logout` or delete `~/.archgate/credentials`.
133+
134+
### Self-update integrity
135+
136+
When you run `archgate upgrade`, the CLI downloads the release binary from GitHub Releases and verifies its SHA256 checksum before extraction. If the checksum does not match, the upgrade is aborted. This protects against tampered downloads due to network interception or compromised mirrors.
137+
138+
## Reporting vulnerabilities
139+
140+
If you discover a security issue in Archgate, please report it responsibly by opening a [GitHub issue](https://github.com/archgate/cli/issues) or contacting the maintainers directly. Do not include exploit code in public issues.

0 commit comments

Comments
 (0)