dsh-permission-rules 0.7.21 → 0.7.22
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/CHANGELOG.md +7 -0
- package/README.en.md +504 -0
- package/README.es.md +504 -0
- package/README.hi.md +504 -0
- package/README.md +309 -245
- package/README.pt.md +504 -0
- package/docs/assets/dsh-permission-rules-flow-dark.svg +58 -0
- package/docs/assets/dsh-permission-rules-flow-light.svg +58 -0
- package/docs/assets/dsh-permission-rules-flow-v5-dark.svg +103 -0
- package/docs/assets/dsh-permission-rules-flow-v5-light.svg +103 -0
- package/docs/assets/dsh-permission-rules-hero-en-dark.webp +0 -0
- package/docs/assets/dsh-permission-rules-hero-en-light.webp +0 -0
- package/docs/assets/dsh-permission-rules-hero-zh-dark.webp +0 -0
- package/docs/assets/dsh-permission-rules-hero-zh-light.webp +0 -0
- package/docs/rules-format.en.md +5 -4
- package/docs/rules-format.md +5 -4
- package/lib/index.js +230 -40
- package/lib/types/config.d.ts +9 -4
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/prose.d.ts +6 -0
- package/lib/types/prose.d.ts.map +1 -1
- package/lib/types/rules.d.ts +74 -15
- package/lib/types/rules.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +59 -12
- package/lib/types/runtime.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +9 -4
- package/src/prose.ts +21 -0
- package/src/rules.ts +131 -24
- package/src/runtime.ts +140 -24
- package/README-es.md +0 -443
- package/README-hi.md +0 -445
- package/README-pt.md +0 -443
- package/README-zh.md +0 -443
package/README.pt.md
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/dsh-permission-rules-hero-en-dark.webp">
|
|
5
|
+
<img src="docs/assets/dsh-permission-rules-hero-en-light.webp" width="100%" alt="O livro de decisões do dsh-permission-rules: a primeira correspondência de uma cadeia ordenada de regras, seu cartão de veredito deny e a faixa do proxy para a saída de shell">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
# dsh-permission-rules
|
|
9
|
+
|
|
10
|
+
**Retome a autoridade de decisão sobre as chamadas de ferramentas: cada allow / deny / ask vive em YAML, de forma determinística, instantânea e auditável.**
|
|
11
|
+
|
|
12
|
+
Um middleware de permissões declarativo no estilo Claude Code para o DeepSeek Harness: as regras decidem o que é conhecido e um modelo revisor decide o que não é; ele também traz uma política de rede em nível de processo no estilo Codex e um proxy local que governam a saída dos subprocessos de shell.
|
|
13
|
+
|
|
14
|
+
[Instalar](#instalação-em-três-passos) · [Configuração](#configuração) · [FAQ](#faq) · [简体中文](README.md) · [English](README.en.md) · [Español](README.es.md) · [Português](README.pt.md) · [हिन्दी](README.hi.md)
|
|
15
|
+
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](#compatibilidade)
|
|
18
|
+
[](https://www.npmjs.com/package/dsh-permission-rules)
|
|
19
|
+
[](https://github.com/PerryLink/dsh-permission-rules/actions/workflows/ci.yml)
|
|
20
|
+
[](#compatibilidade)
|
|
21
|
+
|
|
22
|
+
[](https://github.com/PerryLink/dsh-permission-rules/stargazers)
|
|
23
|
+
[](https://www.npmjs.com/package/dsh-permission-rules)
|
|
24
|
+
[](https://dshfind.com/plugins/PerryLink/dsh-permission-rules?ref=badge)
|
|
25
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/PerryLink/dsh-permission-rules)
|
|
26
|
+
[](https://dsh.market/)
|
|
27
|
+
[](https://gitee.com/perrylink/dsh-permission-rules)
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
dsh plugin --profile <name> add dsh-permission-rules
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<sub>Plugin da comunidade, sem afiliação com a DeepSeek. Repositório canônico: <https://github.com/PerryLink/dsh-permission-rules> (o Gitee é um espelho).</sub>
|
|
36
|
+
|
|
37
|
+
## O que é
|
|
38
|
+
|
|
39
|
+
O `dsh-permission-rules` coloca uma lista ordenada de regras **`allow` / `deny` / `ask`** na frente de cada chamada de ferramenta no waterfall `tools/pre-execute`: determinística, instantânea, auditável e escrita por você em YAML puro. As regras decidem as chamadas que você conhece; as incertas vão para o seam oficial de aprovação, respondido por um modelo revisor ou por uma pessoa. Uma segunda camada adiciona uma política de rede em nível de processo no estilo Codex: a saída dos subprocessos de shell passa por um proxy local HTTP/CONNECT embutido, julgado por regras de rede ou por três modos mapeados nos presets oficiais de sandbox.
|
|
40
|
+
|
|
41
|
+
<picture>
|
|
42
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/dsh-permission-rules-flow-v5-dark.svg">
|
|
43
|
+
<img src="docs/assets/dsh-permission-rules-flow-v5-light.svg" width="100%" alt="Estrutura do dsh-permission-rules: chamadas de ferramentas e saída de shell convergem em uma cadeia ordenada de regras, bifurcam em vereditos de ferramentas e de rede, e fecham com dois eventos de auditoria somente-log">
|
|
44
|
+
</picture>
|
|
45
|
+
|
|
46
|
+
*Estrutura: dois canais (chamadas de ferramentas / saída de shell) convergem em uma cadeia ordenada de regras —a primeira correspondência vence— e bifurcam em vereditos de ferramentas e de rede, fechados por dois eventos de auditoria somente-log.*
|
|
47
|
+
|
|
48
|
+
## Instalação em três passos
|
|
49
|
+
|
|
50
|
+
1. **Instale** (escolha um canal):
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
# canal git (main mais recente; o script prepare compila apenas com dependências de produção)
|
|
54
|
+
dsh plugin --profile web add github:PerryLink/dsh-permission-rules
|
|
55
|
+
# canal npm (versões publicadas)
|
|
56
|
+
dsh plugin --profile web add dsh-permission-rules
|
|
57
|
+
# canal tarball: pnpm pack neste repositório e então
|
|
58
|
+
dsh plugin --profile web add ./dsh-permission-rules-<version>.tgz
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. **Escreva sua primeira regra** (`.dsh/rules.yaml` na raiz do projeto; é opcional, a linha de base de alto risco já vem ativada):
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
# <project>/.dsh/rules.yaml
|
|
65
|
+
rules:
|
|
66
|
+
- match: { tools: [bash, pwsh], params: { command: "git push*" }, paths: ["**/secrets/**"] }
|
|
67
|
+
action: deny
|
|
68
|
+
reason: "No pushes from protected paths"
|
|
69
|
+
|
|
70
|
+
- match: { tools: [edit, write] }
|
|
71
|
+
action: ask
|
|
72
|
+
reason: "File writes need confirmation"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. **Verifique se monta**:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# após reiniciar o perfil, confirme a linha do plugin
|
|
79
|
+
dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
80
|
+
# avalie a seco uma chamada hipotética na superfície de comandos real; nenhuma ferramenta é executada
|
|
81
|
+
/rules test bash {"command":"git push origin main","cwd":"src/secrets/app"}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
"bash" matches rule 1 (deny): 禁止 push 到受保护路径
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Desinstalar**: `dsh plugin --profile web remove dsh-permission-rules` (ou remova a linha do patch do perfil).
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary>Instalar pelo mercado do DSH Desktop</summary>
|
|
92
|
+
|
|
93
|
+
Todos os plugins da PerryLink estão listados no mercado embutido do DSH Desktop: **Mercado → Fontes → Adicionar fonte → cole** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ selecione**. A instalação ainda exige a verificação de identidade npm do mercado e a sua confirmação.
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
## Capacidades principais
|
|
98
|
+
|
|
99
|
+
O `dsh-permission-rules` coloca regras ordenadas na frente de cada chamada de ferramenta em `tools/pre-execute`: determinístico, instantâneo, auditável:
|
|
100
|
+
|
|
101
|
+
- **`deny`** bloqueia a chamada; o `reason` da regra vira o erro visível para o modelo.
|
|
102
|
+
- **`ask`** usa o seam oficial de aprovação (monte o `dsh-auto-review` para um segundo modelo responder, ou uma pessoa responde; sem nenhum dos dois, o harness falha fechado).
|
|
103
|
+
- **`allow`** (e a ausência de correspondência) delega estritamente via `next()`: listeners posteriores nunca são curto-circuitados.
|
|
104
|
+
|
|
105
|
+
Cada acerto **e** cada passagem direta é auditado como evento de sessão `permissionRules/decision` (somente log: nada extra é injetado no contexto do modelo).
|
|
106
|
+
|
|
107
|
+
- **Correspondência rica** — globs de nome de ferramenta (incluindo `mcp__*`), seletores de identidade de agente (`main` / `subagent` / `preset:*`), globs **ou** regex de chaves/valores de argumentos (com negação `!pattern` e a dimensão de chave `absent`), globs de caminhos relativos ao workspace em **qualquer profundidade**, condições de host `when` (variáveis de ambiente, plataforma) e **decomposição de comandos de shell** (`argv`: palavra de comando, tokens de argumento, assinatura de pipe) para correspondência precisa em nível de token.
|
|
108
|
+
- **Linha de base de alto risco embutida** — um conjunto deny/ask incluído (comandos destrutivos, escalada de privilégios, baixar e executar, caminhos sensíveis) ativado por padrão e acrescentado após as regras do usuário, de modo que uma regra mais próxima pode sobrepô-lo; controlado por `builtin.enabled`.
|
|
109
|
+
- **Arquivos de regras hierárquicos** — `searchUp` opcional mescla cada `.dsh/rules.yaml` do cwd da sessão até a raiz do sistema de arquivos, o mais próximo primeiro.
|
|
110
|
+
- **Implantação em simulação** — `enforce: false` audita o que a política *faria* enquanto deixa cada chamada passar.
|
|
111
|
+
- **Recarga a quente** — watch do Chokidar com debounce; uma edição quebrada no nível do arquivo mantém as regras anteriores e nunca derruba o processo, enquanto uma única regra inválida desativa apenas a si mesma. Em um host WSL, ou para um arquivo em `/mnt/<unidade>`, o watch passa a polling porque os eventos nativos não são confiáveis ali.
|
|
112
|
+
- **Ruidosa, mas limitada** — erros de arquivo (YAML inválido e afins) seguem `badFilePolicy`; uma regra inválida (glob/regex ruim, campo/ação desconhecido) desativa apenas a si mesma e aparece na inicialização e em `/rules`, enquanto as demais continuam funcionando (issue #49).
|
|
113
|
+
|
|
114
|
+
### Superfície de comandos e ferramentas
|
|
115
|
+
|
|
116
|
+
| Superfície | Tipo | Notas |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `tools/pre-execute` | listener | Regras allow/deny/ask de primeira correspondência + extração de URLs candidatas de rede |
|
|
119
|
+
| `/rules` | command | `list` · `reload` · `decisions [n]` · `test <tool> <json>` |
|
|
120
|
+
| `permissionRules/decision` | event | Auditoria somente-log de cada acerto e passagem direta |
|
|
121
|
+
| `permissionRules/network` | event | Auditoria da camada proxy para conexões bloqueadas |
|
|
122
|
+
| HTTP/CONNECT proxy | service | Proxy local embutido que governa o tráfego dos subprocessos de shell |
|
|
123
|
+
| settings page | client | Editor de modo de rede, editor de regras, contadores de bloqueios e intercepções recentes com ação de permitir por bloqueio |
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
/rules list the active rules, their source files, and any last-reload error
|
|
127
|
+
/rules list explicit alias for the bare listing
|
|
128
|
+
/rules reload re-read the rule-file chain for this workspace
|
|
129
|
+
/rules decisions [n] show the last n permission decisions of this session (default 10)
|
|
130
|
+
/rules test <tool> <json> dry-evaluate the rules against a hypothetical call
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
O `/rules test` também aceita flags iniciais: `--cwd <dir>`, `--env KEY=VALUE` (repetível), `--agent <selector>` (repetível) e `--platform <name>`. Em cadeias de vários arquivos (p. ex. `searchUp`), cada linha de regra listada é atribuída ao seu próprio arquivo de origem.
|
|
134
|
+
|
|
135
|
+
### Exemplos
|
|
136
|
+
|
|
137
|
+
Estes são os pontos de entrada reais do plugin; instale uma vez e chame um.
|
|
138
|
+
|
|
139
|
+
**Exemplo detalhado** — comando `/rules test bash {"command":"git push origin main","cwd":"src/secrets/app"}`, saída:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
"bash" matches rule 1 (deny): 禁止 push 到受保护路径
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
*Observed from `test/command.spec.ts:204-219 (assertions at :209-213); src/prose.ts:121`*
|
|
146
|
+
|
|
147
|
+
**Ramo de falha** — entrada `/rules test bash {not json`, saída:
|
|
148
|
+
|
|
149
|
+
```text
|
|
150
|
+
Invalid JSON arguments "{not json": the arguments must parse as JSON.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
*Observed from `plugins/dsh-permission-rules/src/prose.ts:123; plugins/dsh-permission-rules/src/runtime.ts:1266-1272; plugins/dsh-permission-rules/test/command.spec.ts:221-228`*
|
|
154
|
+
|
|
155
|
+
**Exemplos curtos**: `tools/pre-execute` · `/rules test <tool> <json>`
|
|
156
|
+
|
|
157
|
+
## Sintaxe de regras
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
# <project>/.dsh/rules.yaml
|
|
161
|
+
rules:
|
|
162
|
+
- match: { tools: [bash, pwsh], params: { command: "git push*" }, paths: ["**/secrets/**"] }
|
|
163
|
+
action: deny
|
|
164
|
+
reason: "No pushes from protected paths"
|
|
165
|
+
|
|
166
|
+
- match: { tools: [edit, write] }
|
|
167
|
+
action: ask
|
|
168
|
+
reason: "File writes need confirmation"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- **Dimensões de correspondência** — `tools` (globs, incl. `mcp__*`), `agents` (`main` / `subagent` / `preset:<name>`; identidade desconhecida nunca corresponde: falha fechado), `params` (globs ou regex de chave/valor, negação `!pattern`, dimensão de chave `absent`), `paths` (globs relativos ao workspace extraídos em qualquer profundidade), `when` (globs/regex de variáveis `env` + lista fechada `platform`) e `network` (`domains` / `ips` / `ports` / `schemes`: globs, curingas, CIDRs, faixas de portas).
|
|
172
|
+
- **Ações** — `allow` / `deny` / `ask`, avaliadas na ordem do arquivo, a primeira correspondência vence.
|
|
173
|
+
- **Metadados de regra** — `enabled: false` (visível mas inerte), `description`, `tags`; campos desconhecidos falham no carregamento.
|
|
174
|
+
- **Schema** — um JSON Schema em [docs/rules-format.schema.json](docs/rules-format.schema.json) (autocompletar do editor via `# yaml-language-server: $schema=...`); o vocabulário completo e uma linha de base de segurança de 5 regras estão em [docs/rules-format.en.md](docs/rules-format.en.md).
|
|
175
|
+
|
|
176
|
+
## Política de rede
|
|
177
|
+
|
|
178
|
+
Uma **política de rede em nível de processo** no estilo Codex: o tráfego dos subprocessos de shell passa por um **proxy HTTP/CONNECT local embutido**, e cada conexão é decidida por regras de rede ordenadas ou por três modos mapeados nos presets oficiais de sandbox:
|
|
179
|
+
|
|
180
|
+
- **`deny-all`** — o preset de sandbox somente leitura: bloqueia toda saída.
|
|
181
|
+
- **`whitelist`** — o preset workspace-write: permite os alvos listados e aplica `unlisted: ask` (ou `deny`) ao resto.
|
|
182
|
+
- **`allow-all`** — o preset danger-full-access: permite tudo.
|
|
183
|
+
- **`auto`** (padrão) — segue o preset de sandbox; em hosts sem o serviço de política de sandbox resolve para `autoFallback` (`allow-all`).
|
|
184
|
+
|
|
185
|
+
- **Correspondência** — `match.network` com `domains` / `ips` / `ports` / `schemes` (globs, curingas, CIDRs, faixas de portas; portas YAML numéricas são aceitas). A extração de URLs candidatas no caminho quente `tools/pre-execute` dispara em argumentos de ferramentas web e URLs embutidas no texto de comandos bash/pwsh; alvos de loopback podem curto-circuitar regras conforme a política `loopback`. Literais IPv6 mapeados de IPv4 são normalizados para a forma IPv4 antes da comparação, e o proxy conecta nos endereços sobre os quais a decisão foi tomada — nunca uma segunda resolução DNS — uma conexão cuja adjudicação não resolveu nenhum endereço falha fechada (502) em vez de discar o nome.
|
|
186
|
+
- **Encadeamento a montante** — `network.upstreamProxy` (padrão `off`) envia as conexões que este proxy **permite** por um proxy a montante: o CONNECT pede um túnel ao upstream (`CONNECT host:port`) e requisições HTTP simples são encaminhadas em forma absoluta. Um alvo bloqueado nunca chega ao upstream — ele continua recebendo o 403 estruturado deste plugin. Dois casos nunca encadeiam mesmo com upstream configurado: um alvo de **loopback** (um proxy fora deste host não roteia o loopback dele) e qualquer decisão produzida por uma **regra com escopo `ips`** (o encadeamento entrega o nome do host ao upstream, então "a conexão cai num endereço que as regras viram" — issue #21 — deixaria de valer exatamente onde as regras se importavam com o endereço; essas decisões continuam discando o endereço adjudicado diretamente), além de um alvo cujo scheme não tem upstream utilizável. O nome do host do próprio upstream é configuração do operador, não entrada do agente, e não é adjudicado por estas regras. Uma URL com credenciais nunca é emitida em bruto: avisos, `/rules network` e o snapshot da página de ajustes mascaram a senha (`http://user:***@host:port`). Upstream inalcançável, timeout (10 s) ou resposta não 2xx resultam em **502** — deliberadamente não há fallback silencioso para a conexão direta, para que uma configuração errada continue visível.
|
|
187
|
+
- **Auditoria** — conexões negadas acrescentam `permissionRules/network` à sessão proprietária (mesma porta adaptativa `ignorable`), com contadores de bloqueios e intercepções recentes em `/rules network` e na página de ajustes.
|
|
188
|
+
- **Diagnóstico** — conexões bloqueadas carregam uma mensagem `[network: …]` nomeando o alvo bloqueado, o modo ou a regra que decidiu e a remediação.
|
|
189
|
+
- **Permitir pela página de ajustes** — cada intercepção recente oferece uma ação **Permitir**. Ela escreve UMA regra mínima `match: { network: { domains: [<host>] } }` / `action: allow` no **topo** (índice 0) do arquivo de regras mais próximo que realmente julga aquela conexão: o arquivo de projeto do workspace ao qual o bloqueio foi atribuído (o `rulesFile` absoluto quando configurado) ou, para um bloqueio de nível de host sem sessão, o arquivo que a cadeia de host resolve (`rulesFile` absoluto → `<processCwd>/<rulesFile>` existente → `fallbackPath` configurado → `<processCwd>/<rulesFile>`, criado); e quando vários workspaces estão carregados, a página oferece primeiro um seletor, porque qualquer cadeia de workspace carregada supera a cadeia de host. Índice 0 porque as regras são de primeira correspondência: acrescentada depois de um `deny` existente, a regra seria texto morto. Somente `domains`, porque essa dimensão inclui subdomínios e ignora porta/scheme, então o host não é rebloqueado na próxima porta; comentários existentes e regras não tocadas sobrevivem, e o texto passa pela mesma porta de validação de uma edição manual. A mudança vale imediatamente — as cadeias de workspace em cache E a cadeia de host sem sessão são relidas, sem reinício e sem `/rules reload` — e a decisão é recalculada, então o aviso relata o resultado REAL e nunca afirma sucesso enquanto a conexão continua bloqueada. Recusa, deixando o arquivo intacto, um workspace desconhecido, um alvo fora das fontes de regras conhecidas, a linha de base embutida somente leitura e um arquivo que não consegue ler ou analisar; uma conexão já permitida não escreve nada. `network.allowHostAction: false` esconde o botão e faz o RPC recusar.
|
|
190
|
+
|
|
191
|
+
## Configuração
|
|
192
|
+
|
|
193
|
+
Todos os ajustes são campos `Config` do Schemastery (alteráveis no cordis.yml). Uma sobreposição por id substitui a linha inteira — repita cada chave necessária. Em um host `0.1.7-alpha` cada campo é declarado `.volatile()`, então também é editável no local pela página de ajustes de Plugins: a edição é confirmada nas referências em execução sem remontar o plugin, o runtime lê o novo valor no próximo uso, e uma mudança em `network.proxyPort`, `proxyBind`, `injectEnv` ou `noProxy` reata o proxy local imediatamente. Os limites numéricos vivem no schema, e o editor de configuração resolve a configuração mesclada contra esse schema antes de persistir, então um valor fora da faixa é recusado no momento da escrita. Em um host cujo Schemastery é anterior a `volatile()`, esses campos ficam restritos ao cordis.yml.
|
|
194
|
+
|
|
195
|
+
Chaves mais usadas:
|
|
196
|
+
|
|
197
|
+
| Chave | Padrão | Significado |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| `rulesFile` | `.dsh/rules.yaml` | Local do arquivo de regras; relativo = resolvido contra o cwd da sessão chamadora, absoluto = global e validado na montagem |
|
|
200
|
+
| `fallbackPath` | *(none)* | Arquivo de regras usado quando a descoberta por cwd não acha nada; validado na montagem |
|
|
201
|
+
| `badFilePolicy` | `fail` | Falhas de arquivo: `fail` faz a chamada pendente falhar ruidosamente; `ignore-with-warning` avisa e continua vazio. Uma regra inválida é sempre isolada: ela é desativada e reportada na inicialização e em `/rules` |
|
|
202
|
+
| `maxRules` | `256` | Limite rígido de regras na cadeia efetiva de fontes |
|
|
203
|
+
| `patternMode` | `glob` | Estilo de padrão de `params`/`paths`/`when.env`: `glob` ou `regex` (nomes de ferramenta são sempre globs) |
|
|
204
|
+
| `watch` | `true` | Watch do Chokidar + recarga ao mudar |
|
|
205
|
+
| `enforce` | `true` | `false` = modo simulação: acertos deny/ask são auditados com marca `dryRun` e toda chamada passa |
|
|
206
|
+
| `language` | `en` | Idioma de saída do `/rules`: `en`, `zh`, `es`, `pt`, `hi` |
|
|
207
|
+
|
|
208
|
+
<details>
|
|
209
|
+
<summary>Tabela de configuração completa (29 chaves)</summary>
|
|
210
|
+
|
|
211
|
+
| Chave | Padrão | Significado |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `rulesFile` | `.dsh/rules.yaml` | Local do arquivo de regras; relativo = resolvido contra o cwd da sessão chamadora, absoluto = global e validado na montagem |
|
|
214
|
+
| `fallbackPath` | *(none)* | Arquivo de regras usado quando a descoberta por cwd não acha nada; validado na montagem |
|
|
215
|
+
| `badFilePolicy` | `fail` | Falhas de arquivo: `fail` faz a chamada pendente falhar ruidosamente; `ignore-with-warning` avisa e continua vazio. Uma regra inválida é sempre isolada: ela é desativada e reportada na inicialização e em `/rules` |
|
|
216
|
+
| `maxRules` | `256` | Limite rígido de regras na cadeia efetiva de fontes |
|
|
217
|
+
| `maxCachedWorkspaces` | `512` | Limite rígido de cargas de regras por workspace em cache (expulsão LRU) |
|
|
218
|
+
| `patternMode` | `glob` | Estilo de padrão de `params`/`paths`/`when.env`: `glob` ou `regex` (nomes de ferramenta são sempre globs) |
|
|
219
|
+
| `watch` | `true` | Watch do Chokidar + recarga ao mudar |
|
|
220
|
+
| `watchStabilityThresholdMs` | `200` | Janela de debounce da recarga (ms) |
|
|
221
|
+
| `language` | `en` | Idioma de saída do `/rules`: `en`, `zh`, `es`, `pt`, `hi` |
|
|
222
|
+
| `caseInsensitivePaths` | *(win32)* | Padrões `paths` e comparação com a raiz do workspace ignoram maiúsculas ASCII; `true` no Windows |
|
|
223
|
+
| `audit` | `all` | Granularidade da auditoria: `all` registra cada acerto E cada passagem direta; `hits` pula as passagens diretas |
|
|
224
|
+
| `searchUp` | `false` | Percorre os diretórios pais a partir do cwd da sessão e mescla cada arquivo de regras encontrado, o mais próximo primeiro |
|
|
225
|
+
| `maxGlobStars` | `2` | Limite rígido de quantificadores `*`/`**` sem limite por padrão glob |
|
|
226
|
+
| `enforce` | `true` | `false` = modo simulação: acertos deny/ask são auditados com marca `dryRun` e toda chamada passa |
|
|
227
|
+
| `allowUnmarkedAudit` | `false` | Hosts pré-marcador descartam a marca `ignorable`; o plugin desativa a auditoria do log de sessão com um aviso. Defina `true` para reativar |
|
|
228
|
+
| `network.enabled` | `true` | Interruptor mestre do proxy, da injeção de ambiente e dos padrões de modo das ferramentas web |
|
|
229
|
+
| `network.mode` | `auto` | Modo de política: `auto` segue o preset de sandbox, ou `deny-all` / `whitelist` / `allow-all` |
|
|
230
|
+
| `network.autoFallback` | `allow-all` | Modo usado quando `auto` não tem serviço de política de sandbox |
|
|
231
|
+
| `network.unlisted` | `ask` | Tratamento no modo whitelist de alvos sem correspondência: `ask` ou `deny` |
|
|
232
|
+
| `network.proxyBind` | `127.0.0.1` | Endereço de bind do proxy local (somente loopback) |
|
|
233
|
+
| `network.proxyPort` | `0` | Porta do proxy local; `0` escolhe uma porta efêmera livre |
|
|
234
|
+
| `network.proxyMaxRecent` | `100` | Limite de registros de bloqueios recentes para a página de ajustes |
|
|
235
|
+
| `network.loopback` | `allow` | Alvos de loopback: `allow` (paridade com Codex) ou `policy` |
|
|
236
|
+
| `network.injectEnv` | `true` | Se variáveis de ambiente de proxy são injetadas nos subprocessos |
|
|
237
|
+
| `network.noProxy` | `clear` | Tratamento do NO_PROXY nos subprocessos: `clear` impõe a política ou `preserve` |
|
|
238
|
+
| `network.upstreamProxy` | `off` | Proxy a montante para as conexões que este plugin PERMITE: `off` conecta direto, `inherit` reutiliza os nomes de proxy do ambiente de lançamento, ou uma URL `http(s)://` explícita |
|
|
239
|
+
| `network.allowHostAction` | `true` | Ação "permitir este host" por bloqueio na página de ajustes: `false` esconde o botão Permitir e faz o RPC `permissionRules/allowHost` recusar (o editor de regras não é afetado) |
|
|
240
|
+
| `builtin.enabled` | `true` | Linha de base de alto risco embutida: `false` desativa por completo o conjunto deny/ask incluído |
|
|
241
|
+
| `builtin.path` | *(incluído)* | Arquivo de linha de base substituto (absoluto ou relativo a `process.cwd()`); validado na montagem |
|
|
242
|
+
|
|
243
|
+
</details>
|
|
244
|
+
|
|
245
|
+
## FAQ
|
|
246
|
+
|
|
247
|
+
<details>
|
|
248
|
+
<summary>Onde ficam os arquivos de regras? O plugin vai escrever nos meus arquivos?</summary>
|
|
249
|
+
|
|
250
|
+
As regras são lidas do disco: `.dsh/rules.yaml` na raiz do projeto, ou o `rulesFile` / `fallbackPath` configurado, mesclados para cima com `searchUp`. O caminho normal de adjudicação não escreve nenhum dado de regra. As únicas superfícies de escrita são o editor de regras da página de ajustes e a ação "Permitir" — esta última escreve uma regra allow mínima no topo de uma fonte de regras conhecida e repassa a mesma validação de parse/compilação de uma edição manual (veja "Política de rede").
|
|
251
|
+
|
|
252
|
+
</details>
|
|
253
|
+
|
|
254
|
+
<details>
|
|
255
|
+
<summary>O que acontece com ask sem o dsh-auto-review montado?</summary>
|
|
256
|
+
|
|
257
|
+
`ask` usa o seam oficial de aprovação: com o `dsh-auto-review` montado, um segundo modelo somente leitura responde; caso contrário, uma pessoa responde; sem nenhum dos dois, o harness falha fechado e a chamada nunca é permitida em silêncio. Este plugin nunca cria subagentes nem chama modelos — produzir uma decisão `ask` é o fim do seu trabalho.
|
|
258
|
+
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
<details>
|
|
262
|
+
<summary>Este proxy intercepta as requisições de saída do próprio host?</summary>
|
|
263
|
+
|
|
264
|
+
Não. O ambiente de proxy injetado cobre os **filhos de shell gerados** e qualquer consumidor que leia os nomes de proxy no momento da requisição. **Não** cobre o tráfego do próprio processo host baseado em `fetch`: o launcher instala o dispatcher global do undici a partir do ambiente de lançamento antes de o primeiro plugin montar, e esse dispatcher roteia pela sua política, não pelo ambiente. Portanto endpoints do provedor **não** precisam de regra de permissão, e `network.injectEnv: false` não tira o processo host de nada. Medido em 2026-09-10 no Node 22; reproduza com `scripts/host-egress-probe.mjs`.
|
|
265
|
+
|
|
266
|
+
</details>
|
|
267
|
+
|
|
268
|
+
<details>
|
|
269
|
+
<summary>Quanto custa no caminho quente?</summary>
|
|
270
|
+
|
|
271
|
+
A avaliação de regras é uma varredura em memória de primeira correspondência — sem chamadas a modelos, sem requisições de rede; acertos e passagens diretas apenas acrescentam auditoria de sessão. Um watch que falha avisa e mantém as regras anteriores. Com conjuntos grandes, defina `audit: hits` para pular as passagens diretas, ou limite a carga com `maxRules` / `maxCachedWorkspaces`.
|
|
272
|
+
|
|
273
|
+
</details>
|
|
274
|
+
|
|
275
|
+
<details>
|
|
276
|
+
<summary>Ele briga com outros plugins por eventos, nomes de comando ou slots?</summary>
|
|
277
|
+
|
|
278
|
+
Não. Os nomes de ferramenta têm namespace e nenhum nome nu é tomado; as chaves de serviço `permissionRules` / `permissionRulesRuntime` não são um seam embutido e nenhum plugin de muitas estrelas pesquisado as fornece; ele não registra slot de cliente nem prefixo `webServer`, e seu patch apenas `insert`a a própria linha; eventos compartilhados sempre delegam via `next()`. O argumento ponto a ponto está em "Interoperabilidade com outros plugins DSH".
|
|
279
|
+
|
|
280
|
+
</details>
|
|
281
|
+
|
|
282
|
+
## Limites de segurança
|
|
283
|
+
|
|
284
|
+
- **Política, não kernel.** Os candidatos de `paths` vêm apenas de um conjunto documentado de chaves de argumento (em qualquer profundidade, com limite de profundidade), e somente caminhos relativos ao workspace correspondem.
|
|
285
|
+
- **Aqui não há revisor.** O plugin nunca cria subagentes nem chama modelos — produzir uma decisão `ask` é o fim do seu trabalho.
|
|
286
|
+
- **Sem mudanças de sandbox.** A política de sandbox em nível de SO pertence ao seam de sandbox, não a este plugin.
|
|
287
|
+
- **Configuração inválida ruidosa.** Campos YAML desconhecidos, ações desconhecidas e padrões ruins são rejeitados no carregamento.
|
|
288
|
+
- **Limites de backtracking.** Padrões glob são limitados a `maxGlobStars` expansões de estrela sem limite; padrões em modo regex rejeitam quantificadores aninhados sem limite e alternâncias literais sobrepostas quantificadas.
|
|
289
|
+
- **Conexão que falha fechada.** Encaminhamentos HTTP simples e túneis CONNECT conectam apenas a endereços adjudicados; um allow cuja adjudicação não resolveu endereço falha fechado com 502 em vez de discar o nome (issue #23).
|
|
290
|
+
|
|
291
|
+
## Permissões e dados
|
|
292
|
+
|
|
293
|
+
- **Permissões**: declara `files:read`, `files:watch`, `files:write`, `session:append` e `network:outbound` em seu manifesto workshop. As decisões `ask` usam o seam oficial de aprovação — nada é reimplementado nem contornado.
|
|
294
|
+
- **Dados**: os arquivos de regras são lidos do disco; a adjudicação normal não escreve dados de regras — as únicas superfícies de escrita são o editor de regras da página de ajustes e a ação "Permitir". Sem chamadas a modelos, sem subagentes revisores.
|
|
295
|
+
- **Log de sessão**: `permissionRules/decision` nunca é injetado no contexto do modelo e é acrescentado com a marca `ignorable: true` do envelope, para que qualquer build do harness carregue o log.
|
|
296
|
+
|
|
297
|
+
## Limitações conhecidas
|
|
298
|
+
|
|
299
|
+
- **Marca de auditoria em hosts pré-marcador e hostis.** `permissionRules/decision` é acrescentado com `ignorable: true`; hosts cujo `Session.append` é anterior à marca (as linhas `0.1.0-rc.1`–`rc.7` e `0.1.1-rc.1`–`rc.7`) a descartam em silêncio, a linha `0.1.2-rc` traz a superfície alpha.5 (nenhuma opção de append escreve a marca), a linha `0.1.2-alpha` recusa eventos de plugin na leitura mesmo marcados, e as linhas `0.1.3-alpha` e `0.1.5-alpha` mantêm a mesma assinatura de append somente-superfície (verificado nos pacotes publicados `0.1.3-alpha.1`/`0.1.5-alpha.1`) — o runtime pré-verifica todas antes do primeiro append e desativa a auditoria do log de sessão com um aviso único. A migração entre gerações também recusa linhas de auditoria marcadas: a porta v1→v2 do `0.1.3-alpha` recusa eventos v1 desconhecidos, e a porta v2→v3 do `0.1.5-alpha` recusa todo evento não classificado (seu inventário é congelado no vocabulário v2 publicado), então faça `strip` das linhas v1 antes de um host 0.1.3 abrir o log e das linhas v2 antes de atualizar para um host 0.1.5. Logs v3 nativos aceitam linhas de plugin marcadas, então só precisam de `repair`. Defina `allowUnmarkedAudit: true` para reativar; repare logs já escritos com `scripts/repair-session-logs.mjs` (seu modo `strip` remove linhas de auditoria onde a marca não ajuda).
|
|
300
|
+
- **Os candidatos de caminho são heurísticos.** Apenas as chaves de argumento documentadas alimentam a correspondência de caminhos, e a correspondência relativa ao workspace ignora maiúsculas ASCII somente com `caseInsensitivePaths` ativo.
|
|
301
|
+
- **Globs são um subconjunto conservador.** Sem expansão de chaves — escreva dois padrões, ou use o modo regex.
|
|
302
|
+
- **A guarda de backtracking regex é estrutural, não exaustiva.** Prefira o modo glob para arquivos não confiáveis.
|
|
303
|
+
- **As requisições de saída do próprio host não passam por este proxy.** O ambiente de proxy injetado cobre os **filhos de shell gerados** — que é o propósito da injeção — e qualquer consumidor que leia os nomes de proxy no momento da requisição. **Não** cobre o tráfego do próprio processo host baseado em `fetch`: o launcher instala o dispatcher global do undici a partir do **ambiente de lançamento** antes de o primeiro plugin montar, esse dispatcher roteia pela sua política e não pelo ambiente, e o Node amostra o ambiente de proxy na partida — então um plugin montado depois não consegue redirecioná-lo. Consequências: endpoints do provedor **não** precisam de regra de permissão, e `network.injectEnv: false` não tira o processo host de nada. Medido em 2026-09-10 no Node 22; reproduza com `scripts/host-egress-probe.mjs`.
|
|
304
|
+
- **O encadeamento a montante cobre apenas o que chega a este proxy.** Em `dsh-v0.1.7-alpha.1` e posteriores, a política de proxy de lançamento do próprio harness e sua camada de ambiente de subprocessos podem ter precedência sobre os nomes de proxy que este plugin injeta, então `network.upstreamProxy` descreve o que acontece com as conexões que este proxy trata — não é uma afirmação sobre cada caminho de saída do processo host.
|
|
305
|
+
|
|
306
|
+
## Interoperabilidade com outros plugins DSH
|
|
307
|
+
|
|
308
|
+
Verificado contra **DSH `0.2.0-rc.2`** (o runtime para o qual este README é publicado) e o conjunto de plugins de muitas estrelas pesquisado em 2026-10-05.
|
|
309
|
+
|
|
310
|
+
Este plugin **não interfere** em outros plugins, incluindo os de muitas estrelas amplamente instalados:
|
|
311
|
+
|
|
312
|
+
- **Sem colisão de nomes de ferramenta.** Toda ferramenta tem namespace; nenhum nome nu de ferramenta incluída ou de outro plugin é registrado.
|
|
313
|
+
- **Sem colisão de chaves de serviço.** Fornece `permissionRules`, `permissionRulesRuntime`; essas chaves não são um seam embutido e não são fornecidas por nenhum plugin de muitas estrelas pesquisado.
|
|
314
|
+
- **Sem colisão de slots.** Não registra chave de slot de cliente, então não disputa um assento de `shadows-shipped-ui`.
|
|
315
|
+
- **Sem colisão de rotas HTTP.** Não registra nenhum prefixo `webServer`.
|
|
316
|
+
- **Sem colisão de camada patch.** O patch do bundle apenas `insert`a a própria linha; nunca sobrescreve o `config` de uma linha embutida.
|
|
317
|
+
- **Sem mutação global.** Não altera protótipos, não reescreve `process.env` nem substitui o dispatcher global de fetch.
|
|
318
|
+
|
|
319
|
+
**Listeners de eventos compartilhados não interferem por construção.** Ele observa os eventos sensíveis à ordem `tools/post-execute`, `tools/pre-execute` com `ctx.on()` — o registro de broadcast do Cordis, onde todo listener roda e nenhum pode deixar outro faminto. **Cada listener aqui delega via `next()`**, então a cadeia nunca é curto-circuitada, e uma mutação é aplicada ao valor que `next()` produziu em vez de substituir seu retorno:
|
|
320
|
+
|
|
321
|
+
- `tools/post-execute` — also used by `cc-safety-net` (1576★).
|
|
322
|
+
- `tools/pre-execute` — also used by `cc-safety-net` (1576★).
|
|
323
|
+
|
|
324
|
+
Evidência estática: o `dsh-plugin-doctor` K10–K13 reporta `pass` em cada verificação deste repositório.
|
|
325
|
+
|
|
326
|
+
## Colaboração com o dsh-auto-review
|
|
327
|
+
|
|
328
|
+
- O `dsh-permission-rules` produz `ask`; o `dsh-auto-review` responde no waterfall `approval/request` com um veredito de segundo modelo somente leitura (ou delega a pessoas). Monte os dois para o ciclo fechado completo.
|
|
329
|
+
- Testado em integração: `permissionRules/decision` → `approval/asked` → `autoReview/verdict` → `approval/decided`, com o revisor substituído por um mock com script.
|
|
330
|
+
- A política de aprovação `never` e toda garantia de falha fechada do harness oficial permanecem intactas.
|
|
331
|
+
|
|
332
|
+
## Reparo de registros de sessão
|
|
333
|
+
|
|
334
|
+
Registros de sessão escritos antes de a marca `ignorable` existir podem ser recusados por builds mais novas do harness (`SessionFormatUnsupportedError`). O `scripts/repair-session-logs.mjs` incluído reescreve apenas as linhas de auditoria alvo para carregar `ignorable: true`, preservando o frame e com backups:
|
|
335
|
+
|
|
336
|
+
```sh
|
|
337
|
+
node scripts/repair-session-logs.mjs scan [--home DIR] # relata linhas estranhas, não muda nada
|
|
338
|
+
node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
339
|
+
node scripts/repair-session-logs.mjs strip [--home DIR] [--dry-run]
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`--home` é por padrão `$DSH_HOME/sessions` (ou `~/.dsh/sessions`). Cada log endereçado por geração é descoberto pelo basename canônico — `session.jsonl`, `session.v2.jsonl`, `session.v3.jsonl`, cada um opcionalmente comprimido com `.zstd` — então escolha o modo pela geração:
|
|
343
|
+
|
|
344
|
+
- **v3 (`session.v3.jsonl`, escrito nativamente pela linha `0.1.5-alpha`)** — o caminho de leitura aceita linhas de plugin marcadas, então `repair` basta.
|
|
345
|
+
- **v2 (`session.v2.jsonl`, escrito pela linha `0.1.3-alpha`)** — `repair` o abre no host que o escreveu, mas a migração v2→v3 do `0.1.5-alpha` recusa todo evento não classificado mesmo marcado: rode `strip` nos logs v2 **antes** de atualizar para um host `0.1.5-alpha`.
|
|
346
|
+
- **v1 (`session.jsonl`)** — a migração v1→v2 do `0.1.3-alpha` recusa eventos v1 desconhecidos mesmo marcados: rode `strip` antes de um host 0.1.3 ou posterior abrir o log pela primeira vez.
|
|
347
|
+
|
|
348
|
+
## Compatibilidade
|
|
349
|
+
|
|
350
|
+
| Superfície | Estado |
|
|
351
|
+
|---|---|
|
|
352
|
+
| Harness | DeepSeek Harness `dsh-v0.2.1-alpha.1` (adaptado em 2026-09-22, cadeia completa de portas): sua configuração de plugin é o contrato de ajustes ao vivo — cada campo é declarado `.volatile()`, o Loader entrega ao `apply` uma referência viva por campo, e uma edição salva é confirmada nas referências em execução SEM remontar o plugin, com valores fora da faixa recusados pelo schema antes de qualquer escrita. Um host cujo Schemastery é anterior a `volatile()` (antes de 3.18.3) ainda monta e roda este plugin — os campos simplesmente ficam como valores comuns e o formulário de configuração ao vivo fica indisponível — porque a marca é aplicada por uma sonda de capacidade, não por suposição. `Session.append` nesta linha ainda não consegue carimbar a marca `ignorable` (seu terceiro argumento é a bolsa de intenção de superfície, e o campo do envelope sobrevive apenas para leituras de logs armazenados), então toda a linha `0.2-alpha` é pré-verificada como sem marca e a auditoria do log de sessão fica desativada por padrão — `append` lê apenas `sourceEventSeqs`/`surfaceOp` e congela o envelope, então não tem caminho de código que possa carimbar a marca; as linhas anteriores `0.1.7-alpha` (adaptada em 2026-09-22), `0.1.5-rc.2` (adaptada em 2026-09-09) e `0.1.3-alpha` mantêm a mesma assinatura de append somente-superfície, e cada pré-lançamento `0.2` é classificado com elas (uma versão ESTÁVEL `0.2.x` fica para a sonda de append, então uma superfície de marca restaurada é adotada sem um novo release do plugin). As migrações de log dessas linhas recusam eventos de plugin não classificados mesmo marcados, então faça `strip` das linhas de auditoria v1 antes de um host `0.1.3-alpha` abrir o log e das linhas v2 antes de um host `0.1.5-alpha` migrá-lo (logs v3 nativos só precisam de `repair`). |
|
|
353
|
+
| Node | `^22.19.0 || >=24.9.0` |
|
|
354
|
+
| Plataformas | Todas (host + cliente web de ajustes) |
|
|
355
|
+
| Modelo | Qualquer (motivos deny/ask aparecem pelos resultados de ferramentas) |
|
|
356
|
+
|
|
357
|
+
## Ecossistema e qualidade
|
|
358
|
+
|
|
359
|
+
- [Espelho Gitee](https://gitee.com/perrylink/dsh-permission-rules) (espelho sincronizado; o GitHub é canônico)
|
|
360
|
+
- [Página do plugin no dshfind](https://dshfind.com/plugins/PerryLink/dsh-permission-rules) (instalações e ranking)
|
|
361
|
+
- [Relatório OpenSSF Scorecard](https://securityscorecards.dev/viewer/?uri=github.com/PerryLink/dsh-permission-rules) (movido por [.github/workflows/scorecard.yml](.github/workflows/scorecard.yml))
|
|
362
|
+
- [Relatório de verificação do dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章) (K10–K13 todos `pass`)
|
|
363
|
+
- [DSH Market](https://dsh.market/) (listado)
|
|
364
|
+
- **Canal da loja 1024**: `npm i -g dsh1024` uma vez, depois `dsh1024 plugin --profile web add dsh-permission-rules` (conta para o ranking de instalações do [deepseek1024.com](https://deepseek1024.com))
|
|
365
|
+
- [Base de conhecimento do ecossistema](https://perrylink.github.io/dsh-plugin-guide/): dados medidos, não marketing: guia de desenvolvimento de plugins · dados de seleção de plugins · critérios de manutenção
|
|
366
|
+
|
|
367
|
+
## Desenvolvimento
|
|
368
|
+
|
|
369
|
+
```sh
|
|
370
|
+
pnpm install # node ^22.19 || >=24.9
|
|
371
|
+
pnpm run typecheck # tsc, src + tests
|
|
372
|
+
pnpm run lint # eslint, src + tests + scripts
|
|
373
|
+
pnpm test # vitest: 24 spec files
|
|
374
|
+
pnpm run test:coverage # coverage gate (90/80/90/90)
|
|
375
|
+
pnpm run build # tsc declarations + tsdown bundles (lib/)
|
|
376
|
+
pnpm run pack:check # build + pack (the published artifact)
|
|
377
|
+
node scripts/check-readme-sync.mjs # porta de sincronização dos README em cinco idiomas (também na CI)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Veja [VERIFICATION.md](VERIFICATION.md) para o registro de verificação end-to-end sem interface. As imagens do README são geradas programaticamente por [`scripts/gen-readme-assets.py`](scripts/gen-readme-assets.py) (Pillow desenha os banners hero de 1440×576 em chinês/inglês e claro/escuro; um SVG de 1100×660 desenhado à mão é a figura de estrutura) e podem ser reexecutadas com segurança.
|
|
381
|
+
|
|
382
|
+
Topics: `dsh`, `dsh-plugin`, `deepseek-harness`, `permission`, `policy`, `allow-deny-ask`, `approval`, `safety`, `network`, `network-policy`, `proxy`
|
|
383
|
+
|
|
384
|
+
## Família e agradecimentos
|
|
385
|
+
|
|
386
|
+
Este projeto faz parte da família de plugins do DeepSeek Harness da [PerryLink](https://github.com/PerryLink) (todos Apache-2.0). Se for útil, **uma estrela ajuda a próxima pessoa a encontrá-lo** — nada fica bloqueado atrás dela.
|
|
387
|
+
|
|
388
|
+
- [@PerryLink](https://github.com/PerryLink) — criador e mantenedor: vocabulário e avaliação de regras, runtime, watch HMR, auditoria do log de sessão, política de rede + proxy e a documentação em cinco idiomas.
|
|
389
|
+
- [@am-i-dead](https://github.com/am-i-dead) — separou os helpers allow-host seguros para o cliente do módulo somente-host (#24).
|
|
390
|
+
- [@22xuan](https://github.com/22xuan) — o relatório detalhado sobre hosts rc.6 descartando em silêncio a marca `ignorable` do evento de auditoria ([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2)) e a discussão upstream do harness; a detecção de capacidades do host na v0.4.1 e a correção da documentação vieram diretamente dessa análise.
|
|
391
|
+
- [@sjh9714](https://github.com/sjh9714) — propôs o corpus compartilhado de vetores de teste de sintaxe de regras ([#4](https://github.com/PerryLink/dsh-permission-rules/issues/4), [#5](https://github.com/PerryLink/dsh-permission-rules/issues/5)), publicado na v0.5.1 como `docs/rule-test-vectors/`, e forneceu os casos limite de decomposição AST na [discussão de design](https://github.com/PerryLink/dsh-permission-rules/discussions/10).
|
|
392
|
+
- [@weipeng1999](https://github.com/weipeng1999) — a proposta de decomposição de comandos baseada em AST ([#8](https://github.com/PerryLink/dsh-permission-rules/issues/8)) por trás da discussão de design.
|
|
393
|
+
- [@alexchenzl](https://github.com/alexchenzl) — a solicitação de listagem no DSH Directory ([#7](https://github.com/PerryLink/dsh-permission-rules/issues/7)).
|
|
394
|
+
- [@zl190](https://github.com/zl190) — relatou e verificou a lacuna de compatibilidade com o harness `0.1.0-rc.7` ([PR #9](https://github.com/PerryLink/dsh-permission-rules/pulls/9)).
|
|
395
|
+
- [@cuohua](https://github.com/cuohua) — relatou que a linha `0.1.1-rc` ainda descarta a marca `ignorable` embora a porta de versão cobrisse apenas `0.1.0` ([#11](https://github.com/PerryLink/dsh-permission-rules/issues/11)); a porta ampliada veio diretamente dessa análise.
|
|
396
|
+
|
|
397
|
+
<details>
|
|
398
|
+
<summary>Expandir todos os plugins</summary>
|
|
399
|
+
|
|
400
|
+
| Plugin | Estado |
|
|
401
|
+
|---|---|
|
|
402
|
+
| **[dsh-aqua-input-check](https://github.com/PerryLink/dsh-aqua-input-check)** | |
|
|
403
|
+
| **[dsh-archive-check](https://github.com/PerryLink/dsh-archive-check)** | |
|
|
404
|
+
| **[dsh-auto-review](https://github.com/PerryLink/dsh-auto-review)** | |
|
|
405
|
+
| **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | 🧊 CONGELADO — ver o README do repo |
|
|
406
|
+
| **[dsh-bid-ca-precheck](https://github.com/PerryLink/dsh-bid-ca-precheck)** | |
|
|
407
|
+
| **[dsh-bid-qual-check](https://github.com/PerryLink/dsh-bid-qual-check)** | |
|
|
408
|
+
| **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | 🧊 CONGELADO — ver o README do repo |
|
|
409
|
+
| **[dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind)** | |
|
|
410
|
+
| **[dsh-click](https://github.com/PerryLink/dsh-click)** | |
|
|
411
|
+
| **[dsh-composer-history](https://github.com/PerryLink/dsh-composer-history)** | |
|
|
412
|
+
| **[dsh-contract-stance](https://github.com/PerryLink/dsh-contract-stance)** | |
|
|
413
|
+
| **[dsh-customs-doc-check](https://github.com/PerryLink/dsh-customs-doc-check)** | |
|
|
414
|
+
| **[dsh-data-quality](https://github.com/PerryLink/dsh-data-quality)** | 🧊 CONGELADO — ver o README do repo |
|
|
415
|
+
| **[dsh-demurrage-ledger](https://github.com/PerryLink/dsh-demurrage-ledger)** | |
|
|
416
|
+
| **[dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck)** | |
|
|
417
|
+
| **[dsh-drill-script-check](https://github.com/PerryLink/dsh-drill-script-check)** | |
|
|
418
|
+
| **[dsh-eia-guide-check](https://github.com/PerryLink/dsh-eia-guide-check)** | |
|
|
419
|
+
| **[dsh-emergency-plan](https://github.com/PerryLink/dsh-emergency-plan)** | |
|
|
420
|
+
| **[dsh-essay-rubric-check](https://github.com/PerryLink/dsh-essay-rubric-check)** | |
|
|
421
|
+
| **[dsh-evidence-check](https://github.com/PerryLink/dsh-evidence-check)** | |
|
|
422
|
+
| **[dsh-export-ctl-check](https://github.com/PerryLink/dsh-export-ctl-check)** | |
|
|
423
|
+
| **[dsh-fast](https://github.com/PerryLink/dsh-fast)** | |
|
|
424
|
+
| **[dsh-fmea-table-check](https://github.com/PerryLink/dsh-fmea-table-check)** | |
|
|
425
|
+
| **[dsh-forecast-penalty](https://github.com/PerryLink/dsh-forecast-penalty)** | |
|
|
426
|
+
| **[dsh-fund-research](https://github.com/PerryLink/dsh-fund-research)** | |
|
|
427
|
+
| **[dsh-github](https://github.com/PerryLink/dsh-github)** | |
|
|
428
|
+
| **[dsh-gongwen-flow-check](https://github.com/PerryLink/dsh-gongwen-flow-check)** | |
|
|
429
|
+
| **[dsh-gongwen-word-check](https://github.com/PerryLink/dsh-gongwen-word-check)** | |
|
|
430
|
+
| **[dsh-guard-plan-qc](https://github.com/PerryLink/dsh-guard-plan-qc)** | |
|
|
431
|
+
| **[dsh-hazchem-check](https://github.com/PerryLink/dsh-hazchem-check)** | |
|
|
432
|
+
| **[dsh-hazplan-check](https://github.com/PerryLink/dsh-hazplan-check)** | |
|
|
433
|
+
| **[dsh-hidden-risk-map](https://github.com/PerryLink/dsh-hidden-risk-map)** | |
|
|
434
|
+
| **[dsh-hs-classify](https://github.com/PerryLink/dsh-hs-classify)** | |
|
|
435
|
+
| **[dsh-icd-rule-check](https://github.com/PerryLink/dsh-icd-rule-check)** | |
|
|
436
|
+
| **[dsh-industry-research](https://github.com/PerryLink/dsh-industry-research)** | |
|
|
437
|
+
| **[dsh-lawcite-adapter](https://github.com/PerryLink/dsh-lawcite-adapter)** | |
|
|
438
|
+
| **[dsh-laya](https://github.com/PerryLink/dsh-laya)** | |
|
|
439
|
+
| **[dsh-lc-doc-check](https://github.com/PerryLink/dsh-lc-doc-check)** | |
|
|
440
|
+
| **[dsh-learning-gap-check](https://github.com/PerryLink/dsh-learning-gap-check)** | |
|
|
441
|
+
| **[dsh-library](https://github.com/PerryLink/dsh-library)** | |
|
|
442
|
+
| **[dsh-local-ai](https://github.com/PerryLink/dsh-local-ai)** | |
|
|
443
|
+
| **[dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions)** | |
|
|
444
|
+
| **[dsh-mask](https://github.com/PerryLink/dsh-mask)** | |
|
|
445
|
+
| **[dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel)** | |
|
|
446
|
+
| **[dsh-medrec-qc](https://github.com/PerryLink/dsh-medrec-qc)** | |
|
|
447
|
+
| **[dsh-memento](https://github.com/PerryLink/dsh-memento)** | 🧊 CONGELADO — ver o README do repo |
|
|
448
|
+
| **[dsh-nsfc-form-check](https://github.com/PerryLink/dsh-nsfc-form-check)** | |
|
|
449
|
+
| **[dsh-nurse-record-check](https://github.com/PerryLink/dsh-nurse-record-check)** | |
|
|
450
|
+
| **[dsh-observe](https://github.com/PerryLink/dsh-observe)** | |
|
|
451
|
+
| **[dsh-origin-rvc-check](https://github.com/PerryLink/dsh-origin-rvc-check)** | |
|
|
452
|
+
| **[dsh-ota-review-check](https://github.com/PerryLink/dsh-ota-review-check)** | |
|
|
453
|
+
| **[dsh-output-styles](https://github.com/PerryLink/dsh-output-styles)** | |
|
|
454
|
+
| **[dsh-paper-doc-adapter](https://github.com/PerryLink/dsh-paper-doc-adapter)** | |
|
|
455
|
+
| **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | |
|
|
456
|
+
| **[dsh-permit-report-check](https://github.com/PerryLink/dsh-permit-report-check)** | |
|
|
457
|
+
| **[dsh-pipeline-check](https://github.com/PerryLink/dsh-pipeline-check)** | |
|
|
458
|
+
| **[dsh-pleading-draft](https://github.com/PerryLink/dsh-pleading-draft)** | |
|
|
459
|
+
| **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | 🚫 APOSENTADO (repo arquivado) |
|
|
460
|
+
| **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | |
|
|
461
|
+
| **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | 🚫 APOSENTADO (repo arquivado) |
|
|
462
|
+
| **[dsh-plugin-upgrade](https://github.com/PerryLink/dsh-plugin-upgrade)** | 🚫 APOSENTADO (repo arquivado) |
|
|
463
|
+
| **[dsh-policy-brief-draft](https://github.com/PerryLink/dsh-policy-brief-draft)** | |
|
|
464
|
+
| **[dsh-power-loss-split](https://github.com/PerryLink/dsh-power-loss-split)** | |
|
|
465
|
+
| **[dsh-power-ticket-check](https://github.com/PerryLink/dsh-power-ticket-check)** | |
|
|
466
|
+
| **[dsh-ppap-check](https://github.com/PerryLink/dsh-ppap-check)** | |
|
|
467
|
+
| **[dsh-protest-deadline](https://github.com/PerryLink/dsh-protest-deadline)** | |
|
|
468
|
+
| **[dsh-railway-window](https://github.com/PerryLink/dsh-railway-window)** | |
|
|
469
|
+
| **[dsh-repair-order-qc](https://github.com/PerryLink/dsh-repair-order-qc)** | |
|
|
470
|
+
| **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | |
|
|
471
|
+
| **[dsh-review-reply-check](https://github.com/PerryLink/dsh-review-reply-check)** | |
|
|
472
|
+
| **[dsh-rulefile-check](https://github.com/PerryLink/dsh-rulefile-check)** | |
|
|
473
|
+
| **[dsh-safety-brief-check](https://github.com/PerryLink/dsh-safety-brief-check)** | |
|
|
474
|
+
| **[dsh-score](https://github.com/PerryLink/dsh-score)** | 🧊 CONGELADO — ver o README do repo |
|
|
475
|
+
| **[dsh-session-sync](https://github.com/PerryLink/dsh-session-sync)** | |
|
|
476
|
+
| **[dsh-site-log-check](https://github.com/PerryLink/dsh-site-log-check)** | |
|
|
477
|
+
| **[dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security)** | |
|
|
478
|
+
| **[dsh-soe-decision-check](https://github.com/PerryLink/dsh-soe-decision-check)** | |
|
|
479
|
+
| **[dsh-soilwater-check](https://github.com/PerryLink/dsh-soilwater-check)** | |
|
|
480
|
+
| **[dsh-sop-sync-check](https://github.com/PerryLink/dsh-sop-sync-check)** | |
|
|
481
|
+
| **[dsh-spc-gbt-adapter](https://github.com/PerryLink/dsh-spc-gbt-adapter)** | |
|
|
482
|
+
| **[dsh-talk](https://github.com/PerryLink/dsh-talk)** | |
|
|
483
|
+
| **[dsh-tender-extract](https://github.com/PerryLink/dsh-tender-extract)** | |
|
|
484
|
+
| **[dsh-tender-matrix](https://github.com/PerryLink/dsh-tender-matrix)** | |
|
|
485
|
+
| **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | |
|
|
486
|
+
| **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | 🚫 APOSENTADO (repo arquivado) |
|
|
487
|
+
| **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | |
|
|
488
|
+
| **[dsh-warranty-calc](https://github.com/PerryLink/dsh-warranty-calc)** | |
|
|
489
|
+
| **[dsh-budget](https://github.com/PerryLink/dsh-budget)** | 🧊 CONGELADO — ver o README do repo |
|
|
490
|
+
| **[dsh-claude-move](https://github.com/PerryLink/dsh-claude-move)** | 🧊 CONGELADO — ver o README do repo |
|
|
491
|
+
| **[dsh-defend](https://github.com/PerryLink/dsh-defend)** | 🧊 CONGELADO — ver o README do repo |
|
|
492
|
+
| **[dsh-draw](https://github.com/PerryLink/dsh-draw)** | 🧊 CONGELADO — ver o README do repo |
|
|
493
|
+
| **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | 🧊 CONGELADO — ver o README do repo |
|
|
494
|
+
| **[dsh-background-agents](https://github.com/PerryLink/dsh-background-agents)** | 🚫 APOSENTADO (repo arquivado) |
|
|
495
|
+
| **[dsh-session-pin](https://github.com/PerryLink/dsh-session-pin)** | 🚫 APOSENTADO (repo arquivado) |
|
|
496
|
+
| **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | 🚫 APOSENTADO (repo arquivado) |
|
|
497
|
+
|
|
498
|
+
</details>
|
|
499
|
+
|
|
500
|
+
## Licença
|
|
501
|
+
|
|
502
|
+
[LICENSE](LICENSE) (Apache License 2.0) © 2026 dsh-permission-rules contributors
|
|
503
|
+
|
|
504
|
+
<p align="center"><sub>Apache-2.0 · Plugin da comunidade, sem afiliação com a DeepSeek.</sub></p>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="440" viewBox="0 0 1200 440" role="img" aria-label="dsh-permission-rules data flow">
|
|
2
|
+
<title>dsh-permission-rules data flow</title>
|
|
3
|
+
<rect width="1200" height="440" rx="18" fill="#0d1117"/>
|
|
4
|
+
<text x="56" y="60" fill="#f0f6fc" font-family="ui-sans-serif,system-ui,sans-serif" font-size="30" font-weight="700">dsh-permission-rules</text>
|
|
5
|
+
<text x="56" y="94" fill="#8b949e" font-family="ui-monospace,monospace" font-size="15">tools/pre-execute · ordered allow / deny / ask · YAML</text>
|
|
6
|
+
<line x1="56" y1="118" x2="1144" y2="118" stroke="#30363d"/>
|
|
7
|
+
<rect x="30" y="140" width="130" height="104" rx="14" fill="#161b22" stroke="#30363d" stroke-width="2"/>
|
|
8
|
+
<text x="95" y="180" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="14" font-weight="700">tool call</text>
|
|
9
|
+
<text x="95" y="208" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">arguments</text>
|
|
10
|
+
<path d="M169 192H182" stroke="#8b949e" stroke-width="2.5"/>
|
|
11
|
+
<path d="M176 186l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
12
|
+
<rect x="196" y="140" width="145" height="104" rx="14" fill="#161b22" stroke="#4B67B4" stroke-width="2"/>
|
|
13
|
+
<text x="268" y="180" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="13" font-weight="700">tools/pre-execute</text>
|
|
14
|
+
<text x="268" y="208" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">waterfall</text>
|
|
15
|
+
<path d="M350 192H363" stroke="#8b949e" stroke-width="2.5"/>
|
|
16
|
+
<path d="M357 186l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
17
|
+
<rect x="377" y="140" width="145" height="104" rx="14" fill="#161b22" stroke="#4B67B4" stroke-width="2"/>
|
|
18
|
+
<text x="449" y="180" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="12" font-weight="700">.dsh/rules.yaml</text>
|
|
19
|
+
<text x="449" y="208" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">first match wins</text>
|
|
20
|
+
<path d="M531 192H544" stroke="#8b949e" stroke-width="2.5"/>
|
|
21
|
+
<path d="M538 186l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
22
|
+
<rect x="558" y="140" width="190" height="104" rx="14" fill="#161b22" stroke="#30363d" stroke-width="2"/>
|
|
23
|
+
<text x="653" y="180" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="12" font-weight="700">allow · ask · deny</text>
|
|
24
|
+
<text x="653" y="208" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">next() · approval · reason</text>
|
|
25
|
+
<path d="M757 192H770" stroke="#8b949e" stroke-width="2.5"/>
|
|
26
|
+
<path d="M764 186l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
27
|
+
<rect x="784" y="140" width="215" height="104" rx="14" fill="#161b22" stroke="{'light': '#16a34a', 'dark': '#3fb950'}" stroke-width="2"/>
|
|
28
|
+
<text x="891" y="180" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="13" font-weight="700">permissionRules/decision</text>
|
|
29
|
+
<text x="891" y="208" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">audit · log-only</text>
|
|
30
|
+
<path d="M1008 192H1021" stroke="#8b949e" stroke-width="2.5"/>
|
|
31
|
+
<path d="M1015 186l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
32
|
+
<rect x="1035" y="140" width="135" height="104" rx="14" fill="#161b22" stroke="#30363d" stroke-width="2"/>
|
|
33
|
+
<text x="1102" y="180" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="13" font-weight="700">session log</text>
|
|
34
|
+
<text x="1102" y="208" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">ignorable</text>
|
|
35
|
+
<circle cx="627" cy="224" r="4.5" fill="#3fb950"/>
|
|
36
|
+
<circle cx="653" cy="224" r="4.5" fill="#d29922"/>
|
|
37
|
+
<circle cx="679" cy="224" r="4.5" fill="#f85149"/>
|
|
38
|
+
<text x="56" y="278" fill="#8b949e" font-family="ui-monospace,monospace" font-size="12">network policy</text>
|
|
39
|
+
<rect x="81" y="296" width="220" height="84" rx="12" fill="#161b22" stroke="#30363d" stroke-width="1.5"/>
|
|
40
|
+
<text x="191" y="330" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="11.5" font-weight="700">shell child</text>
|
|
41
|
+
<text x="191" y="354" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">spawned process</text>
|
|
42
|
+
<path d="M309 338H339" stroke="#8b949e" stroke-width="2.5"/>
|
|
43
|
+
<path d="M333 332l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
44
|
+
<rect x="357" y="296" width="220" height="84" rx="12" fill="#161b22" stroke="#30363d" stroke-width="1.5"/>
|
|
45
|
+
<text x="467" y="330" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="11.5" font-weight="700">HTTP/CONNECT proxy</text>
|
|
46
|
+
<text x="467" y="354" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">127.0.0.1 · proxyPort</text>
|
|
47
|
+
<path d="M585 338H615" stroke="#8b949e" stroke-width="2.5"/>
|
|
48
|
+
<path d="M609 332l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
49
|
+
<rect x="633" y="296" width="230" height="84" rx="12" fill="#161b22" stroke="#30363d" stroke-width="1.5"/>
|
|
50
|
+
<text x="748" y="330" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="11.5" font-weight="700">domains · ips · ports · schemes</text>
|
|
51
|
+
<text x="748" y="354" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">network rules or mode</text>
|
|
52
|
+
<path d="M871 338H901" stroke="#8b949e" stroke-width="2.5"/>
|
|
53
|
+
<path d="M895 332l7 6-7 6" fill="none" stroke="#8b949e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
54
|
+
<rect x="919" y="296" width="200" height="84" rx="12" fill="#161b22" stroke="#30363d" stroke-width="1.5"/>
|
|
55
|
+
<text x="1019" y="330" fill="#f0f6fc" text-anchor="middle" font-family="ui-monospace,monospace" font-size="11.5" font-weight="700">403 · upstream</text>
|
|
56
|
+
<text x="1019" y="354" fill="#8b949e" text-anchor="middle" font-family="ui-monospace,monospace" font-size="10.5">blocked | allowed</text>
|
|
57
|
+
<text x="56" y="418" fill="#8b949e" font-family="ui-monospace,monospace" font-size="12">builtin · searchUp · watch · enforce: false · /rules test · /rules reload</text>
|
|
58
|
+
</svg>
|