chrome-agent 0.9.0 → 0.11.0

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/README.md CHANGED
@@ -73,7 +73,7 @@ chrome-agent screenshot
73
73
  ## How It Works
74
74
 
75
75
  ```
76
- chrome-agent v0.9.0 (Rust, ~10.6K lines, 3 MB binary)
76
+ chrome-agent v0.11.0 (Rust, ~11.5K lines, 3 MB binary)
77
77
 
78
78
  │ WebSocket (Chrome DevTools Protocol)
79
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-agent",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Browser automation for AI agents. Single binary, zero dependencies, CDP direct.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { createHash } from 'crypto';
3
4
  import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync } from 'fs';
4
5
  import { get } from 'https';
5
6
  import { arch, platform } from 'os';
@@ -64,6 +65,60 @@ async function downloadFile(url, destination) {
64
65
  }).catch((error) => { rmSync(tempPath, { force: true }); throw error; });
65
66
  }
66
67
 
68
+ /** Fetch a small text asset (the checksum file) into memory. */
69
+ async function fetchText(url) {
70
+ return new Promise((resolve, reject) => {
71
+ const request = (currentUrl, redirects = 10) => {
72
+ get(currentUrl, {
73
+ headers: { Accept: 'text/plain', 'User-Agent': `chrome-agent/${version}` },
74
+ }, (response) => {
75
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
76
+ response.resume();
77
+ if (redirects === 0) { reject(new Error('Too many redirects')); return; }
78
+ request(new URL(response.headers.location, currentUrl), redirects - 1);
79
+ return;
80
+ }
81
+ if (response.statusCode !== 200) {
82
+ response.resume();
83
+ reject(new Error(`HTTP ${response.statusCode} from ${currentUrl}`));
84
+ return;
85
+ }
86
+ let body = '';
87
+ response.setEncoding('utf8');
88
+ response.on('data', (chunk) => { body += chunk; });
89
+ response.on('end', () => resolve(body));
90
+ response.on('error', reject);
91
+ }).on('error', reject).setTimeout(30_000, function() { this.destroy(new Error('Timeout')); });
92
+ };
93
+ request(url);
94
+ });
95
+ }
96
+
97
+ export function sha256File(path) {
98
+ return createHash('sha256').update(readFileSync(path)).digest('hex');
99
+ }
100
+
101
+ /**
102
+ * Compare a downloaded file against the checksum published beside it.
103
+ *
104
+ * The binary is fetched over the network and chmod +x'd; without this there was nothing
105
+ * anywhere in the chain saying the bytes are the ones that were built. A mismatch deletes
106
+ * the file rather than leaving a half-trusted binary on disk: a corrupted-but-running
107
+ * binary surfaces as confusing behaviour later, which is worse than a failed install.
108
+ */
109
+ export function verifyChecksum(actualHex, expectedRaw, binaryName) {
110
+ const expected = String(expectedRaw).trim().split(/\s+/)[0].toLowerCase();
111
+ if (!/^[0-9a-f]{64}$/.test(expected)) {
112
+ throw new Error(`Malformed checksum for ${binaryName}: "${String(expectedRaw).trim().slice(0, 80)}"`);
113
+ }
114
+ if (actualHex.toLowerCase() !== expected) {
115
+ throw new Error(
116
+ `Checksum mismatch for ${binaryName}: expected ${expected}, got ${actualHex}. ` +
117
+ 'The download was corrupted or the release asset does not match what was published.'
118
+ );
119
+ }
120
+ }
121
+
67
122
  async function main() {
68
123
  const targetKey = getTargetKey();
69
124
  const binaryName = targetKey ? supportedTargets[targetKey] : null;
@@ -90,11 +145,20 @@ async function main() {
90
145
  console.log(`chrome-agent: downloading native binary for ${platform()}-${arch()}...`);
91
146
 
92
147
  await downloadFile(url, binaryPath);
148
+ try {
149
+ verifyChecksum(sha256File(binaryPath), await fetchText(`${url}.sha256`), binaryName);
150
+ } catch (error) {
151
+ rmSync(binaryPath, { force: true });
152
+ throw error;
153
+ }
93
154
  if (platform() !== 'win32') chmodSync(binaryPath, 0o755);
94
- console.log(`chrome-agent: installed ${binaryName}`);
155
+ console.log(`chrome-agent: installed ${binaryName} (sha256 verified)`);
95
156
  }
96
157
 
97
- main().catch((error) => {
98
- console.error(`chrome-agent postinstall failed: ${error.message}`);
99
- process.exitCode = 1;
100
- });
158
+ // Importable for the unit tests without running the install.
159
+ if (process.env.CHROME_AGENT_POSTINSTALL_NOOP !== '1') {
160
+ main().catch((error) => {
161
+ console.error(`chrome-agent postinstall failed: ${error.message}`);
162
+ process.exitCode = 1;
163
+ });
164
+ }