xkat-cli 1.1.5 → 1.1.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.
- package/README.md +4 -0
- package/bin/checksums.json +7 -0
- package/package.json +1 -1
- package/scripts/generate-checksums.js +42 -0
- package/scripts/install.js +51 -3
package/README.md
CHANGED
|
@@ -55,6 +55,10 @@ xKat Agent includes the following safeguards:
|
|
|
55
55
|
domains are accepted.
|
|
56
56
|
3. **Audit log**: Every connection attempt and file-change event is recorded
|
|
57
57
|
locally with a timestamp.
|
|
58
|
+
4. **Binary Integrity Check**: During installation, the postinstall script downloads
|
|
59
|
+
the platform-specific prebuilt Rust binary over HTTPS and cryptographically
|
|
60
|
+
verifies its hash (SHA-256) against a build-time checksum manifest to prevent
|
|
61
|
+
MITM, tampering, or supply chain injection.
|
|
58
62
|
|
|
59
63
|
## Releasing (maintainers)
|
|
60
64
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"xkat-agent-macos-aarch64": "1b34b5fa60c2097d034f5ae45ca5dcb67bfc1ad9ee67c4b84dd2be0c33debb0e",
|
|
3
|
+
"xkat-agent-macos-x64": "ffe1da9a5fe4c04976c9cbd527795d17647b6c7cc6597f5dd72bd23931301b6b",
|
|
4
|
+
"xkat-agent-linux-x64": "0963c85e9e92e36d157c69f7bd1a51285743ec3a41be71908f543b064e697e93",
|
|
5
|
+
"xkat-agent-linux-aarch64": "30e2c06adb61ad5e1e0ef77584a72a525201222e68049d2568084340c5f7bbc1",
|
|
6
|
+
"xkat-agent-win-x64.exe": "ec9a5b4c0b08db139fd7861df7de900b94d5f41577621780e99b8ce4ea40e9d3"
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// The artifacts directory is created by actions/download-artifact at the repository root
|
|
6
|
+
const artifactsDir = path.resolve(__dirname, '../../..', 'artifacts');
|
|
7
|
+
const destFile = path.resolve(__dirname, '..', 'bin', 'checksums.json');
|
|
8
|
+
|
|
9
|
+
const targets = [
|
|
10
|
+
'xkat-agent-macos-aarch64',
|
|
11
|
+
'xkat-agent-macos-x64',
|
|
12
|
+
'xkat-agent-linux-x64',
|
|
13
|
+
'xkat-agent-linux-aarch64',
|
|
14
|
+
'xkat-agent-win-x64.exe',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const checksums = {};
|
|
18
|
+
|
|
19
|
+
console.log(`🔍 Scanning for artifacts in: ${artifactsDir}`);
|
|
20
|
+
|
|
21
|
+
for (const target of targets) {
|
|
22
|
+
// In upload-r2/download-artifact, each artifact is downloaded into its own folder:
|
|
23
|
+
// artifacts/<artifact-name>/<artifact-name>
|
|
24
|
+
const filePath = path.join(artifactsDir, target, target);
|
|
25
|
+
if (!fs.existsSync(filePath)) {
|
|
26
|
+
console.error(`❌ Missing artifact for target ${target} at ${filePath}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
30
|
+
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
|
31
|
+
checksums[target] = hash;
|
|
32
|
+
console.log(`✅ Calculated hash for ${target}: ${hash}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Make sure the destination directory exists
|
|
36
|
+
const destDir = path.dirname(destFile);
|
|
37
|
+
if (!fs.existsSync(destDir)) {
|
|
38
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(destFile, JSON.stringify(checksums, null, 2));
|
|
42
|
+
console.log(`✅ Generated checksums manifest: ${destFile}`);
|
package/scripts/install.js
CHANGED
|
@@ -7,6 +7,7 @@ const os = require('os');
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const https = require('https');
|
|
10
|
+
const crypto = require('crypto');
|
|
10
11
|
|
|
11
12
|
// Version is the single source of truth from package.json, so a release bump
|
|
12
13
|
// (npm version patch) automatically points install at the matching R2 folder.
|
|
@@ -28,6 +29,16 @@ function getPlatformTarget() {
|
|
|
28
29
|
return map[`${platform}-${arch}`] || null;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function calculateSha256(filePath) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const hash = crypto.createHash('sha256');
|
|
35
|
+
const stream = fs.createReadStream(filePath);
|
|
36
|
+
stream.on('data', (data) => hash.update(data));
|
|
37
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
38
|
+
stream.on('error', (err) => reject(err));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
async function main() {
|
|
32
43
|
const target = getPlatformTarget();
|
|
33
44
|
|
|
@@ -40,10 +51,35 @@ async function main() {
|
|
|
40
51
|
const binDir = path.join(__dirname, '..', 'binaries');
|
|
41
52
|
const destPath = path.join(binDir, target);
|
|
42
53
|
|
|
43
|
-
//
|
|
54
|
+
// Load checksums manifest if it exists
|
|
55
|
+
const checksumsPath = path.join(__dirname, '..', 'bin', 'checksums.json');
|
|
56
|
+
let expectedHash = null;
|
|
57
|
+
if (fs.existsSync(checksumsPath)) {
|
|
58
|
+
try {
|
|
59
|
+
const checksums = JSON.parse(fs.readFileSync(checksumsPath, 'utf8'));
|
|
60
|
+
expectedHash = checksums[target] || null;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.warn(`⚠️ Failed to parse checksums manifest: ${e.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Skip if already downloaded and verified
|
|
44
67
|
if (fs.existsSync(destPath)) {
|
|
45
|
-
|
|
46
|
-
|
|
68
|
+
if (expectedHash) {
|
|
69
|
+
try {
|
|
70
|
+
const currentHash = await calculateSha256(destPath);
|
|
71
|
+
if (currentHash === expectedHash) {
|
|
72
|
+
console.log(`✅ xkat-agent binary already present and verified: ${target}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(`⚠️ Hash mismatch for existing binary. Re-downloading...`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.log(`⚠️ Failed to verify existing binary hash: ${err.message}. Re-downloading...`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
console.log(`✅ xkat-agent binary already present: ${target}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
47
83
|
}
|
|
48
84
|
|
|
49
85
|
console.log(`📥 Downloading xkat-agent... (${target})`);
|
|
@@ -57,6 +93,18 @@ async function main() {
|
|
|
57
93
|
|
|
58
94
|
try {
|
|
59
95
|
await downloadFile(url, destPath);
|
|
96
|
+
|
|
97
|
+
// Verify integrity after download
|
|
98
|
+
if (expectedHash) {
|
|
99
|
+
const downloadedHash = await calculateSha256(destPath);
|
|
100
|
+
if (downloadedHash !== expectedHash) {
|
|
101
|
+
// Remove corrupted/tampered file
|
|
102
|
+
try { fs.unlinkSync(destPath); } catch (_) {}
|
|
103
|
+
throw new Error(`Integrity check failed: expected ${expectedHash}, got ${downloadedHash}`);
|
|
104
|
+
}
|
|
105
|
+
console.log(`✅ Integrity verified: SHA-256 match`);
|
|
106
|
+
}
|
|
107
|
+
|
|
60
108
|
// Grant execute permission (Unix only)
|
|
61
109
|
if (os.platform() !== 'win32') {
|
|
62
110
|
fs.chmodSync(destPath, 0o755);
|