@hasna/logs 0.3.28 → 0.3.30

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/dist/cli/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  runJob,
9
9
  structuredLogToEntry,
10
10
  validateStructuredLogReferences
11
- } from "../index-y2y0mdtd.js";
11
+ } from "../index-k9w7zfsv.js";
12
12
  import {
13
13
  PACKAGE_VERSION,
14
14
  createPage,
@@ -30,7 +30,7 @@ import {
30
30
  searchTestReports,
31
31
  summarizeLogs,
32
32
  validateUniversalEventInput
33
- } from "../index-qk8dbvbc.js";
33
+ } from "../index-mx0f61s2.js";
34
34
  import {
35
35
  getStorageStatus,
36
36
  storagePull,
@@ -8,7 +8,7 @@ import {
8
8
  redactValue,
9
9
  saveSnapshot,
10
10
  touchPage
11
- } from "./index-qk8dbvbc.js";
11
+ } from "./index-mx0f61s2.js";
12
12
  import {
13
13
  getEventStoreDataDir
14
14
  } from "./index-t3x838zw.js";
@@ -237,7 +237,7 @@ async function fireAlert(db, rule, count) {
237
237
  }
238
238
 
239
239
  // src/lib/ingest.ts
240
- import { randomBytes } from "crypto";
240
+ import { createHash, randomBytes } from "crypto";
241
241
 
242
242
  // src/lib/event-bus.ts
243
243
  class LogEventBus {
@@ -508,9 +508,26 @@ function readPositiveInt(name, fallback) {
508
508
 
509
509
  // src/lib/redaction.ts
510
510
  var REDACTED = "[REDACTED]";
511
- var SENSITIVE_KEY = /(?:authorization|cookie|set-cookie|api[_-]?key|token|secret|password|passwd|pwd|private[_-]?key|access[_-]?token|refresh[_-]?token|session[_-]?secret|client[_-]?secret)/i;
512
- var SENSITIVE_FLAG = /^(?:authorization|auth|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?secret)$/i;
513
- var SENSITIVE_FLAG_NAME = /(?:authorization|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?secret)/i;
511
+ var SENSITIVE_KEY = /(?:authorization|cookie|set-cookie|credentials?\b|api[_-]?key|token|secret|password|passwd|pwd|private[_-]?key|access[_-]?token|refresh[_-]?token|session[_-]?secret|client[_-]?(?:secret|credentials?))/i;
512
+ var SENSITIVE_FLAG = /^(?:authorization|auth|credentials?|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?(?:secret|credentials?))$/i;
513
+ var SENSITIVE_FLAG_NAME = /(?:authorization|credentials?\b|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?(?:secret|credentials?))/i;
514
+ var LOG_ENTRY_REDACTABLE_TOP_LEVEL_FIELDS = [
515
+ "id",
516
+ "source_event_id",
517
+ "service",
518
+ "machine_id",
519
+ "repo_id",
520
+ "app_id",
521
+ "process_id",
522
+ "run_id",
523
+ "trace_id",
524
+ "span_id",
525
+ "parent_span_id",
526
+ "session_id",
527
+ "release_id",
528
+ "environment",
529
+ "agent"
530
+ ];
514
531
  var STRING_PATTERNS = [
515
532
  {
516
533
  label: "openlogs_canary",
@@ -564,12 +581,12 @@ var STRING_PATTERNS = [
564
581
  },
565
582
  {
566
583
  label: "secret_assignment",
567
- pattern: /\b(api[_-]?key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|client[_-]?secret)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
568
- replacement: (_match, key) => `${key}=${REDACTED}`
584
+ pattern: /(?<![?&])\b(credentials?|api[_-]?key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|client[_-]?(?:secret|credentials?))\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
585
+ replacement: (match, key, value) => isKnownNonSecretCredentialAssignment(key, value) ? match : `${key}=${REDACTED}`
569
586
  },
570
587
  {
571
588
  label: "secret_flag_argument",
572
- pattern: /(--[A-Za-z0-9._-]*(?:authorization|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?secret)[A-Za-z0-9._-]*\s+)(?:"[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
589
+ pattern: /(--[A-Za-z0-9._-]*(?:authorization|credentials?(?!ed)|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?(?:secret|credentials?(?!ed)))[A-Za-z0-9._-]*\s+)(?:"[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
573
590
  replacement: (_match, prefix) => `${prefix}${REDACTED}`
574
591
  },
575
592
  {
@@ -579,13 +596,21 @@ var STRING_PATTERNS = [
579
596
  },
580
597
  {
581
598
  label: "secret_query_param",
582
- pattern: /([?&](?:api[_-]?key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|auth|code)=)[^&#\s]+/gi,
599
+ pattern: /([?&](?:credentials?|api[_-]?key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|client[_-]?credentials?|auth|code)=)[^&#\s]+/gi,
583
600
  replacement: (_match, prefix) => `${prefix}${REDACTED}`
584
601
  }
585
602
  ];
586
603
  function redactLogEntry(entry) {
587
604
  const reports = [];
588
605
  const next = { ...entry };
606
+ for (const field of LOG_ENTRY_REDACTABLE_TOP_LEVEL_FIELDS) {
607
+ const value = entry[field];
608
+ if (typeof value !== "string")
609
+ continue;
610
+ const result = redactString(value, field);
611
+ next[field] = result.value;
612
+ reports.push(result.report);
613
+ }
589
614
  if (typeof entry.message === "string") {
590
615
  const result = redactString(entry.message, "message");
591
616
  next.message = result.value;
@@ -622,11 +647,13 @@ function redactString(input, path = "$") {
622
647
  for (const { label, pattern, replacement } of STRING_PATTERNS) {
623
648
  let matched = false;
624
649
  output = output.replace(pattern, (...args) => {
625
- matched = true;
626
- replacements += 1;
627
- if (typeof replacement === "function")
628
- return replacement(args[0] ?? "", ...args.slice(1));
629
- return replacement;
650
+ const original = args[0] ?? "";
651
+ const next = typeof replacement === "function" ? replacement(original, ...args.slice(1)) : replacement;
652
+ if (next !== original) {
653
+ matched = true;
654
+ replacements += 1;
655
+ }
656
+ return next;
630
657
  });
631
658
  if (matched)
632
659
  fields.push(`${path}:${label}`);
@@ -669,7 +696,7 @@ function redactValue(input, path = "$", depth = 0) {
669
696
  const reports = [];
670
697
  for (const [key, value] of Object.entries(input)) {
671
698
  const childPath = `${path}.${key}`;
672
- if (SENSITIVE_KEY.test(key) && value !== null && value !== undefined) {
699
+ if (shouldRedactSensitiveKeyValue(key, value)) {
673
700
  values[key] = REDACTED;
674
701
  reports.push({ applied: true, fields: [childPath], replacements: 1 });
675
702
  continue;
@@ -705,6 +732,24 @@ function isSensitiveFlag(value) {
705
732
  return false;
706
733
  return SENSITIVE_FLAG.test(normalized) || SENSITIVE_FLAG_NAME.test(normalized) || SENSITIVE_KEY.test(normalized.replace(/-/g, "_"));
707
734
  }
735
+ function shouldRedactSensitiveKeyValue(key, value) {
736
+ if (value === null || value === undefined)
737
+ return false;
738
+ if (!SENSITIVE_KEY.test(key))
739
+ return false;
740
+ return !isKnownNonSecretCredentialMode(key, value);
741
+ }
742
+ function isKnownNonSecretCredentialMode(key, value) {
743
+ return key.toLowerCase() === "credentials" && typeof value === "string" && isKnownFetchCredentialMode(value);
744
+ }
745
+ function isKnownNonSecretCredentialAssignment(key, value) {
746
+ return key.toLowerCase() === "credentials" && isKnownFetchCredentialMode(value);
747
+ }
748
+ function isKnownFetchCredentialMode(value) {
749
+ const trimmed = value.trim();
750
+ const unquoted = trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'") ? trimmed.slice(1, -1) : trimmed;
751
+ return /^(?:include|omit|same-origin)$/i.test(unquoted);
752
+ }
708
753
 
709
754
  // src/lib/ingest.ts
710
755
  var ERROR_LEVELS = new Set(["warn", "error", "fatal"]);
@@ -712,7 +757,8 @@ function ingestLog(db, entry) {
712
757
  return withEventStoreLock(db, () => ingestLogLocked(db, entry));
713
758
  }
714
759
  function ingestLogLocked(db, entry) {
715
- const eventId = entry.id ?? createEventId();
760
+ const eventIdRedaction = typeof entry.id === "string" ? redactString(entry.id, "id") : null;
761
+ const eventId = entry.id ? eventIdRedaction?.report.applied ? createRedactedEventId(entry.id) : entry.id : createEventId();
716
762
  const existing = db.prepare("SELECT * FROM logs WHERE id = ?").get(eventId);
717
763
  if (existing)
718
764
  return existing;
@@ -730,6 +776,14 @@ function ingestLogLocked(db, entry) {
730
776
  };
731
777
  const redacted = redactLogEntry(normalized);
732
778
  const safeEntry = redacted.value;
779
+ if (eventIdRedaction?.report.applied) {
780
+ const report = mergeRedactionReports(eventIdRedaction.report, redacted.report);
781
+ safeEntry.metadata = {
782
+ ...safeEntry.metadata ?? {},
783
+ redaction: redactionMetadata(report)
784
+ };
785
+ }
786
+ const safeSourceEventId = safeEntry.source_event_id ?? null;
733
787
  const identity = extractIdentity(safeEntry);
734
788
  const envelope = createLogEnvelope(safeEntry, eventId, eventTime, ingestTime, identity);
735
789
  const write = appendRawEvent(db, envelope);
@@ -758,7 +812,7 @@ function ingestLogLocked(db, entry) {
758
812
  indexRawEvent(db, {
759
813
  event_id: eventId,
760
814
  schema_version: envelope.schema_version,
761
- source_event_id: sourceEventId,
815
+ source_event_id: safeSourceEventId,
762
816
  event_type: envelope.type,
763
817
  event_time: eventTime,
764
818
  ingest_time: ingestTime,
@@ -898,6 +952,10 @@ function stringMetadata(metadata, key) {
898
952
  function createEventId() {
899
953
  return randomBytes(16).toString("hex");
900
954
  }
955
+ function createRedactedEventId(value) {
956
+ const digest = createHash("sha256").update(value).digest("hex").slice(0, 32);
957
+ return `log_redacted_${digest}`;
958
+ }
901
959
 
902
960
  // src/lib/package-meta.ts
903
961
  import { existsSync, readFileSync } from "fs";
@@ -1282,7 +1340,7 @@ function clampNonNegativeInt2(value, fallback) {
1282
1340
  }
1283
1341
 
1284
1342
  // src/lib/universal-ingest.ts
1285
- import { createHash, randomBytes as randomBytes2 } from "crypto";
1343
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
1286
1344
  var UNIVERSAL_EVENT_TYPES = [
1287
1345
  "log",
1288
1346
  "exception",
@@ -1802,7 +1860,7 @@ function normalizeIsoTime(value, field) {
1802
1860
  function deterministicSourceEventId(source, sourceEventId) {
1803
1861
  if (!sourceEventId)
1804
1862
  return;
1805
- const digest = createHash("sha256").update(source).update("\x00").update(sourceEventId).digest("hex").slice(0, 32);
1863
+ const digest = createHash2("sha256").update(source).update("\x00").update(sourceEventId).digest("hex").slice(0, 32);
1806
1864
  return `evt_src_${digest}`;
1807
1865
  }
1808
1866
  function compactObject(value) {
package/dist/mcp/index.js CHANGED
@@ -93,7 +93,7 @@ import {
93
93
  searchTestReports,
94
94
  summarizeLogs,
95
95
  validateUniversalEventInput
96
- } from "../index-qk8dbvbc.js";
96
+ } from "../index-mx0f61s2.js";
97
97
  import {
98
98
  getStoragePg,
99
99
  getStorageStatus,
@@ -8,7 +8,7 @@ import {
8
8
  startScheduler,
9
9
  structuredLogPayloadToEntries,
10
10
  validateStructuredLogReferences
11
- } from "../index-y2y0mdtd.js";
11
+ } from "../index-k9w7zfsv.js";
12
12
  import {
13
13
  countLogs
14
14
  } from "../index-gcd14q2f.js";
@@ -50,7 +50,7 @@ import {
50
50
  updateAlertRule,
51
51
  updateProject,
52
52
  validateUniversalEventInput
53
- } from "../index-qk8dbvbc.js";
53
+ } from "../index-mx0f61s2.js";
54
54
  import {
55
55
  getDb,
56
56
  getIssue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/logs",
3
- "version": "0.3.28",
3
+ "version": "0.3.30",
4
4
  "description": "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,10 @@
19
19
  "logs-mcp": "./dist/mcp/index.js",
20
20
  "logs-serve": "./dist/server/index.js"
21
21
  },
22
- "files": ["dist", "dashboard/dist"],
22
+ "files": [
23
+ "dist",
24
+ "dashboard/dist"
25
+ ],
23
26
  "scripts": {
24
27
  "build": "rm -rf dist && bun build src/cli/index.ts src/mcp/index.ts src/server/index.ts src/index.ts src/storage.ts --outdir dist --target bun --splitting --external playwright --external playwright-core --external electron --external chromium-bidi --external lighthouse",
25
28
  "build:dashboard": "cd dashboard && bun run build",