auroq-os 2.6.6 → 2.6.7

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.
@@ -1,7 +1,7 @@
1
1
  project:
2
2
  name: "Auroq OS"
3
3
  type: greenfield
4
- version: 2.6.6
4
+ version: 2.6.7
5
5
  installedAt: '2026-03-16'
6
6
 
7
7
  ide:
@@ -53,11 +53,15 @@ function safePath(file) {
53
53
  cursor = path.dirname(cursor);
54
54
  }
55
55
  }
56
- function powershell(script, input) {
56
+ // Windows only. The vault logic lives in win-vault.ps1, shipped as plain readable text next to this file:
57
+ // no encoded command, no script text built here. Data goes through stdin as UTF-8; the answer is hex on stdout.
58
+ // The script is started by relative name from its own folder, so quotes, brackets or apostrophes in the
59
+ // student's folder names never reach the PowerShell command line.
60
+ function powershell(action, input) {
57
61
  // Windows PowerShell 5.1 must build its own module path when launched through Node/pwsh 7.
58
62
  // Keep the caller's environment unchanged, including differently cased Windows names.
59
63
  const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => name.toLowerCase() !== 'psmodulepath'));
60
- const result = spawnSync('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(`$ErrorActionPreference='Stop'; $PSModuleAutoLoadingPreference='None'; ${script}`, 'utf16le').toString('base64')], { input, env, encoding: 'utf8', timeout: 5000, windowsHide: true, maxBuffer: 65536 });
64
+ const result = spawnSync('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', '.\\win-vault.ps1', action], { cwd: __dirname, input, env, encoding: 'utf8', timeout: 5000, windowsHide: true, maxBuffer: 65536 });
61
65
  if (result.status !== 0 || result.error) throw new Error('SECURE_STORAGE_UNAVAILABLE');
62
66
  return result.stdout.trim();
63
67
  }
@@ -65,10 +69,10 @@ function protectMany(dirs) {
65
69
  const unique = [...new Set(dirs)]; if (!unique.length) return;
66
70
  for (const dir of unique) { safePath(dir); fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); }
67
71
  if (process.platform === 'win32') {
68
- // Core .NET APIs avoid the variable cold cost of preparing PowerShell cmdlet modules.
72
+ // Core .NET APIs (win-vault.ps1) avoid the variable cold cost of preparing PowerShell cmdlet modules.
69
73
  // Keep the exact same private DACL: current owner + SYSTEM, no inherited external access.
70
- const encoded = unique.map(dir => `'${Buffer.from(dir).toString('base64')}'`).join(',');
71
- powershell(`$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User; foreach($encoded in @(${encoded})){ $p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($encoded)); $acl=[Security.AccessControl.DirectorySecurity]::new(); $acl.SetOwner($sid); $acl.SetAccessRuleProtection($true,$false); foreach($s in @($sid,[Security.Principal.SecurityIdentifier]::new('S-1-5-18'))){ $rule=[Security.AccessControl.FileSystemAccessRule]::new($s,[Security.AccessControl.FileSystemRights]::FullControl,[Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',[Security.AccessControl.PropagationFlags]::None,[Security.AccessControl.AccessControlType]::Allow); [void]$acl.AddAccessRule($rule) }; [void][IO.Directory]::SetAccessControl($p,$acl) }`);
74
+ // The script confirms with "ok <count>": an empty or truncated .ps1 exits 0 without doing anything, and must not pass for success.
75
+ if (powershell('protect', unique.map(dir => path.resolve(dir)).join('\n')) !== `ok ${unique.length}`) throw new Error('SECURE_STORAGE_UNAVAILABLE');
72
76
  } else {
73
77
  for (const dir of unique) {
74
78
  fs.chmodSync(dir, 0o700);
@@ -119,14 +123,19 @@ function keyFor(ctx, create = false) {
119
123
  const file = path.join(ctx.accountDir, 'key.dpapi');
120
124
  if (!fs.existsSync(file)) {
121
125
  if (!create) throw new Error('SECURE_STORAGE_UNAVAILABLE');
122
- const wrapped = powershell("[void][Reflection.Assembly]::Load('System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'); $b=[byte[]]::new(32); $rng=[Security.Cryptography.RandomNumberGenerator]::Create(); try { $rng.GetBytes($b); [Console]::Write([Convert]::ToBase64String([Security.Cryptography.ProtectedData]::Protect($b,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser))) } finally { $rng.Dispose(); [Array]::Clear($b,0,$b.Length) }");
126
+ const created = powershell('create-key');
127
+ if (!/^(?:[a-f0-9]{2})+$/.test(created)) throw new Error('SECURE_STORAGE_UNAVAILABLE');
128
+ const wrapped = Buffer.from(created, 'hex').toString('base64');
123
129
  if (!/^[A-Za-z0-9+/]+={0,2}$/.test(wrapped) || Buffer.from(wrapped, 'base64').length < 32) throw new Error('SECURE_STORAGE_UNAVAILABLE');
124
130
  try { atomic(file, wrapped, true); } catch (e) { if (e.code !== 'EEXIST') throw e; }
125
131
  }
126
132
  safePath(file);
127
- const wrapped = fs.readFileSync(file, 'utf8');
128
- const raw = powershell("[void][Reflection.Assembly]::Load('System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'); $b=[Convert]::FromBase64String([Console]::In.ReadToEnd().Trim()); $plain=[Security.Cryptography.ProtectedData]::Unprotect($b,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser); try { [Console]::Write([Convert]::ToBase64String($plain)) } finally { [Array]::Clear($plain,0,$plain.Length) }", wrapped);
129
- const key = Buffer.from(raw, 'base64'); if (key.length !== 32) throw new Error('SECURE_STORAGE_UNAVAILABLE'); return key;
133
+ // key.dpapi keeps its original format: the DPAPI blob as base64 text. Only hex goes through the PowerShell side.
134
+ const wrapped = fs.readFileSync(file, 'utf8').trim();
135
+ if (wrapped.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(wrapped)) throw new Error('SECURE_STORAGE_UNAVAILABLE');
136
+ const raw = powershell('open-key', Buffer.from(wrapped, 'base64').toString('hex'));
137
+ if (!/^[a-f0-9]{64}$/.test(raw)) throw new Error('SECURE_STORAGE_UNAVAILABLE');
138
+ return Buffer.from(raw, 'hex');
130
139
  }
131
140
  throw new Error('UNSUPPORTED_OS_KEYSTORE');
132
141
  }
@@ -0,0 +1,102 @@
1
+ # win-vault.ps1 - cofre local do Auroq OS no Windows (captura AHI).
2
+ #
3
+ # Faz so isto, no computador do proprio aluno, e nada mais:
4
+ # protect deixa as pastas recebidas privadas: so o dono da conta do Windows e o SISTEMA
5
+ # acessam, sem heranca de permissao de fora.
6
+ # create-key gera uma chave aleatoria de 32 bytes e a protege com o DPAPI do Windows
7
+ # (escopo do usuario atual: so este usuario, neste computador, consegue abrir).
8
+ # open-key abre com o DPAPI uma chave que o create-key protegeu antes.
9
+ #
10
+ # Nao acessa a rede, nao baixa nem executa codigo de fora, nao inicia outros programas e nao grava
11
+ # arquivo nenhum: so troca a permissao das pastas recebidas (protect) ou escreve o resultado na
12
+ # saida padrao. Quem chama e o security.cjs, desta mesma pasta, por -File, passando a acao como
13
+ # argumento. Os dados entram por stdin em UTF-8 (uma pasta por linha em protect; a chave protegida
14
+ # em hex em open-key) e a resposta sai por stdout: "ok N" em protect (N pastas protegidas) e a chave
15
+ # em hex nas outras duas. Qualquer erro, ou resposta fora desse formato, e tratado pelo security.cjs
16
+ # como cofre indisponivel (falha fechada).
17
+ #
18
+ # Usa so a API do .NET, sem cmdlets: preparar os modulos do PowerShell numa maquina fria passava
19
+ # do limite de 5 segundos do security.cjs.
20
+ param([string]$Action)
21
+
22
+ $ErrorActionPreference = 'Stop'
23
+ $PSModuleAutoLoadingPreference = 'None'
24
+
25
+ # stdin em bytes crus, lidos como UTF-8: o console do Windows usa outra codificacao (OEM) e
26
+ # estragaria o acento no nome da pasta do aluno.
27
+ function Get-StdinText {
28
+ $stream = [Console]::OpenStandardInput()
29
+ $buffer = [IO.MemoryStream]::new()
30
+ try { $stream.CopyTo($buffer) } finally { $stream.Dispose() }
31
+ return [Text.UTF8Encoding]::new($false).GetString($buffer.ToArray())
32
+ }
33
+
34
+ function ConvertTo-Hex([byte[]]$Bytes) {
35
+ return [BitConverter]::ToString($Bytes).Replace('-', '').ToLowerInvariant()
36
+ }
37
+
38
+ function ConvertFrom-Hex([string]$Hex) {
39
+ if ($Hex.Length -eq 0 -or ($Hex.Length % 2) -ne 0) { throw 'hex invalido' }
40
+ $bytes = [byte[]]::new([int]($Hex.Length / 2))
41
+ for ($i = 0; $i -lt $bytes.Length; $i++) { $bytes[$i] = [Convert]::ToByte($Hex.Substring(2 * $i, 2), 16) }
42
+ return , $bytes
43
+ }
44
+
45
+ function Protect-Folders([string]$Text) {
46
+ $owner = [Security.Principal.WindowsIdentity]::GetCurrent().User
47
+ $system = [Security.Principal.SecurityIdentifier]::new('S-1-5-18')
48
+ $count = 0
49
+ foreach ($line in $Text.Split([char[]]@([char]10), [StringSplitOptions]::RemoveEmptyEntries)) {
50
+ $folder = $line.TrimEnd([char]13)
51
+ if ($folder.Length -eq 0) { continue }
52
+ # Caminho relativo seria resolvido a partir da pasta do hook, nao da pasta do aluno.
53
+ if (-not [IO.Path]::IsPathRooted($folder)) { throw 'caminho nao absoluto' }
54
+ $acl = [Security.AccessControl.DirectorySecurity]::new()
55
+ $acl.SetOwner($owner)
56
+ $acl.SetAccessRuleProtection($true, $false)
57
+ foreach ($who in @($owner, $system)) {
58
+ $rule = [Security.AccessControl.FileSystemAccessRule]::new($who, [Security.AccessControl.FileSystemRights]::FullControl, [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit', [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
59
+ [void]$acl.AddAccessRule($rule)
60
+ }
61
+ [void][IO.Directory]::SetAccessControl($folder, $acl)
62
+ $count++
63
+ }
64
+ if ($count -eq 0) { throw 'nenhuma pasta recebida' }
65
+ [Console]::Write("ok $count")
66
+ }
67
+
68
+ function Import-DpapiAssembly {
69
+ [void][Reflection.Assembly]::Load('System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
70
+ }
71
+
72
+ function New-ProtectedKey {
73
+ Import-DpapiAssembly
74
+ $key = [byte[]]::new(32)
75
+ $random = [Security.Cryptography.RandomNumberGenerator]::Create()
76
+ try {
77
+ $random.GetBytes($key)
78
+ $wrapped = [Security.Cryptography.ProtectedData]::Protect($key, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
79
+ [Console]::Write((ConvertTo-Hex $wrapped))
80
+ } finally {
81
+ $random.Dispose()
82
+ [Array]::Clear($key, 0, $key.Length)
83
+ }
84
+ }
85
+
86
+ function Open-ProtectedKey([string]$Text) {
87
+ Import-DpapiAssembly
88
+ $wrapped = ConvertFrom-Hex $Text.Trim()
89
+ $key = [Security.Cryptography.ProtectedData]::Unprotect($wrapped, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
90
+ try {
91
+ [Console]::Write((ConvertTo-Hex $key))
92
+ } finally {
93
+ [Array]::Clear($key, 0, $key.Length)
94
+ }
95
+ }
96
+
97
+ switch ($Action) {
98
+ 'protect' { Protect-Folders (Get-StdinText) }
99
+ 'create-key' { New-ProtectedKey }
100
+ 'open-key' { Open-ProtectedKey (Get-StdinText) }
101
+ default { throw 'acao desconhecida' }
102
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 2.6.7 — 2026-10-16
2
+
3
+ ### Antivírus Kaspersky não bloqueia mais a atualização no Windows
4
+
5
+ - Uma aluna com Kaspersky não conseguiu atualizar: o antivírus barrou o arquivo `security.cjs` do cofre local da captura (alerta "Cavalo de Troia", análise heurística) e a instalação parou. Era falso positivo, mas o jeito de chamar o PowerShell lembrava um programa malicioso: o comando ia embaralhado em base64, e o mesmo arquivo trocava permissões de pasta.
6
+ - Agora o PowerShell roda um script legível e versionado, `.claude/hooks/ahi/win-vault.ps1`, chamado por arquivo. Caminhos e dados vão por entrada padrão, nada embaralhado na linha de comando. O arquivo tem comentário dizendo tudo o que faz: deixa as pastas do AHI privadas (dono + SISTEMA, sem herança) e protege a chave local com o DPAPI do Windows. Não usa rede, não baixa nada e não executa texto de fora.
7
+ - Segurança igual à anterior: mesma permissão privada, mesma proteção DPAPI do usuário atual, limite de 5 segundos e falha fechada (sem cofre, nada é gravado). O `key.dpapi` mantém o formato; quem já tinha chave guardada continua abrindo e a chave nova abre nas versões antigas.
8
+ - A manutenção (`atualiza o sistema`) leva o `win-vault.ps1` junto, antes do `security.cjs`, e o inventário de integridade passa a exigi-lo.
9
+ - Testes novos (`tests/ahi-windows-vault.test.mjs`): nenhum arquivo publicado pode voltar a usar comando do PowerShell em base64; o script é ASCII, sem rede e sem base64; o contrato com o PowerShell (argumentos, pasta, entrada, falha fechada); no Windows de verdade, compatibilidade do `key.dpapi` nos dois sentidos e pastas com apóstrofo, colchetes e acento.
10
+ - Mantem `arcane-mcp@0.7.0`. Instaladores do repositorio preparados para 2.6.7.
11
+
1
12
  ## 2.6.6 — 2026-10-03
2
13
 
3
14
  ### Squad Edição Euriliana no Pack
package/README.md CHANGED
@@ -246,13 +246,13 @@ agents/ → Seu exercito (companion, workers, minds, squads)
246
246
 
247
247
  ## Observabilidade e integridade do motor
248
248
 
249
- Apos publicacao, `npx -y auroq-os@2.6.6 doctor` mostra a fila local segura e os comandos pinados de
250
- manutencao/diagnostico. `npx -y auroq-os@2.6.6 manutencao` preserva o caminho existente
249
+ Apos publicacao, `npx -y auroq-os@2.6.7 doctor` mostra a fila local segura e os comandos pinados de
250
+ manutencao/diagnostico. `npx -y auroq-os@2.6.7 manutencao` preserva o caminho existente
251
251
  de consentimento legado, studentFiles, customizacoes e dados do negocio. Nao ha
252
252
  migracao forcada. O registro MCP aponta para `arcane-mcp@0.7.0` — o pin mora no campo
253
253
  `arcaneMcpPin` do package.json (fonte unica; o servidor le o mesmo campo no npm).
254
254
  `npm run release:verify` recusa publicar com um pin que o npm nao resolva.
255
- Os instaladores do repositorio ficam preparados para 2.6.6; as copias publicas so recebem
255
+ Os instaladores do repositorio ficam preparados para 2.6.7; as copias publicas so recebem
256
256
  a versao apos os gates nativos e a verificacao do tarball publicado.
257
257
  O pacote inclui `lib/runtime-integrity.json`: inventario SHA-256 gerado depois da montagem da casca, verificavel com `npm run integrity:verify` dentro do pacote extraido. Ele comprova consistencia do conteudo; nao e uma assinatura de autoria.
258
258
 
package/bin/auroq-os.js CHANGED
@@ -1749,6 +1749,7 @@ async function init({ pasta = null, maintenance = false, modoAgente = false, mig
1749
1749
  // Hooks
1750
1750
  '.claude/hooks/synapse-engine.cjs',
1751
1751
  '.claude/hooks/ahi-capture.cjs',
1752
+ '.claude/hooks/ahi/win-vault.ps1', // 2.6.7 — antes do security.cjs, que o chama
1752
1753
  '.claude/hooks/ahi/security.cjs',
1753
1754
  '.claude/hooks/ahi/runtime.cjs',
1754
1755
  '.claude/hooks/ahi/worker.cjs',
@@ -589,7 +589,7 @@ function Invoke-AuroqLogin {
589
589
  # sucesso. Usado como pergunta de sim/nao, ele dizia "voce ja estava logado"
590
590
  # pra quem nunca logou, pulava o portao e deixava a conta sem acesso chegar ate
591
591
  # a criacao da pasta.
592
- & (Get-AuroqNpx) -y auroq-os@2.6.6 check-access
592
+ & (Get-AuroqNpx) -y auroq-os@2.6.7 check-access
593
593
  if ($LASTEXITCODE -ne 0) {
594
594
  Write-AuroqLog 'login' 'recusado'
595
595
  Write-Host ''
@@ -677,7 +677,7 @@ function Invoke-AuroqInit {
677
677
  if ($env:AUROQ_INSTALL_DRY_RUN) { Write-AuroqOk 'Sistema - seria montado aqui'; return }
678
678
 
679
679
  Set-Location $script:AuroqPastaAlvo
680
- & (Get-AuroqNpx) -y auroq-os@2.6.6 instalar
680
+ & (Get-AuroqNpx) -y auroq-os@2.6.7 instalar
681
681
  if ($LASTEXITCODE -ne 0) {
682
682
  Write-AuroqLog 'init' 'falhou'
683
683
  Write-AuroqErro 'A montagem do sistema parou no meio.' 'Rode o mesmo comando de novo. Se repetir, mande pro suporte o arquivo install.log da pasta .auroq.'
@@ -219,9 +219,9 @@ auroq_tem_terminal() {
219
219
  # recebem pedaco do script no lugar da resposta dele.
220
220
  auroq_npx_aluno() {
221
221
  if [ -t 0 ]; then
222
- npx -y auroq-os@2.6.6 "$@"
222
+ npx -y auroq-os@2.6.7 "$@"
223
223
  else
224
- npx -y auroq-os@2.6.6 "$@" < /dev/tty
224
+ npx -y auroq-os@2.6.7 "$@" < /dev/tty
225
225
  fi
226
226
  }
227
227
 
@@ -2,7 +2,7 @@
2
2
  "schema": 1,
3
3
  "algorithm": "sha256",
4
4
  "scope": "packaged-runtime-and-cascas",
5
- "version": "2.6.6",
5
+ "version": "2.6.7",
6
6
  "files": [
7
7
  {
8
8
  "path": ".auroq-core/constitution.md",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "path": ".auroq-core/core-config.yaml",
13
- "sha256": "ebd8a96492b433bd4b37475920233989ec2a69f383d67f4af1e130e6e52cc052"
13
+ "sha256": "59ba6487bbd6defc87ccda7d174503c8d18af089e284b61cb6f202a646778548"
14
14
  },
15
15
  {
16
16
  "path": ".auroq-core/core/synapse/context/context-builder.js",
@@ -178,7 +178,11 @@
178
178
  },
179
179
  {
180
180
  "path": ".claude/hooks/ahi/security.cjs",
181
- "sha256": "dc27db1392acd0abd8102490860bfad6a94276bbc92f63e086dc997dbe6b208c"
181
+ "sha256": "ff8b4f7069a0a17506e4ea75aeb6cc0fd40cebe350e2e59048372380f059a3c6"
182
+ },
183
+ {
184
+ "path": ".claude/hooks/ahi/win-vault.ps1",
185
+ "sha256": "b3332d3387ed2eea0990bd589cdf0213623e7e30b4daa187bb2930948bbdac29"
182
186
  },
183
187
  {
184
188
  "path": ".claude/hooks/ahi/worker.cjs",
@@ -206,7 +210,7 @@
206
210
  },
207
211
  {
208
212
  "path": "bin/auroq-os.js",
209
- "sha256": "793af8782292df5d108944ec1696b46a028455fef9c6d62b800a51e731ace1c6"
213
+ "sha256": "3ba86acde44b47edfae407e328500855eaa86281b0429da1717d517a0a7e56a3"
210
214
  },
211
215
  {
212
216
  "path": "lib/ahi/client.mjs",
@@ -258,7 +262,7 @@
258
262
  },
259
263
  {
260
264
  "path": "package.json",
261
- "sha256": "e595250e2aec2457f0017693cbe86ec291bc2f764e9da8c7af762c455e67cd4e"
265
+ "sha256": "cce3f831abf590fffbd6af0356a6b0899b17ba7f8a5964dcfced577637e3be19"
262
266
  },
263
267
  {
264
268
  "path": "scripts/build-pack.mjs",
@@ -278,7 +282,7 @@
278
282
  },
279
283
  {
280
284
  "path": "scripts/runtime-integrity.mjs",
281
- "sha256": "af0cb75622dcf4c049da3fc053817087648a86fc31df3be4762e974279eb87a3"
285
+ "sha256": "d2d6d30fa3a288c019a4f7e6d06ba25742775258e5631466e48a9aeb55d030c5"
282
286
  },
283
287
  {
284
288
  "path": "scripts/sync-codex-skills.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auroq-os",
3
- "version": "2.6.6",
3
+ "version": "2.6.7",
4
4
  "arcaneMcpPin": "0.7.0",
5
5
  "description": "Auroq OS — Sistema Operacional de IA para Experts",
6
6
  "bin": {
@@ -27,7 +27,7 @@ export const REQUIRED_FILES = [
27
27
  '.claude/commands/AuroqOS/agents/ops.md', '.claude/commands/auroq-companion.md',
28
28
  '.claude/hooks/synapse-engine.cjs', '.claude/hooks/auroq-sync.cjs',
29
29
  '.claude/hooks/ahi-capture.cjs', '.claude/hooks/ahi/runtime.cjs',
30
- '.claude/hooks/ahi/security.cjs', '.claude/hooks/ahi/worker.cjs', '.claude/hooks/ahi/auth-config.cjs',
30
+ '.claude/hooks/ahi/security.cjs', '.claude/hooks/ahi/win-vault.ps1', '.claude/hooks/ahi/worker.cjs', '.claude/hooks/ahi/auth-config.cjs',
31
31
  ];
32
32
  const PROTECTED_TREES = ['lib/', '.auroq-core/core/synapse/', '.claude/hooks/'];
33
33
  const FORBIDDEN_PREFIXES = ['business/', 'docs/knowledge/', 'casca/', '.git/', '.pack-casca-backup/'];