@hasna/logs 0.3.29 → 0.3.31

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-89jb7jg9.js";
11
+ } from "../index-a0gz0zzc.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-dbhpykkz.js";
33
+ } from "../index-he072p17.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-dbhpykkz.js";
11
+ } from "./index-he072p17.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 {
@@ -511,6 +511,23 @@ var REDACTED = "[REDACTED]";
511
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
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
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",
@@ -586,6 +603,14 @@ var STRING_PATTERNS = [
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;
@@ -633,6 +658,12 @@ function redactString(input, path = "$") {
633
658
  if (matched)
634
659
  fields.push(`${path}:${label}`);
635
660
  }
661
+ const cookieResult = redactCookieHeaderText(output);
662
+ if (cookieResult.replacements > 0) {
663
+ output = cookieResult.value;
664
+ fields.push(`${path}:cookie_header`);
665
+ replacements += cookieResult.replacements;
666
+ }
636
667
  return {
637
668
  value: output,
638
669
  report: { applied: replacements > 0, fields, replacements }
@@ -701,6 +732,191 @@ function redactionMetadata(report) {
701
732
  function emptyReport() {
702
733
  return { applied: false, fields: [], replacements: 0 };
703
734
  }
735
+ function redactCookieHeaderText(input) {
736
+ const ranges = [];
737
+ const keyPattern = /set-cookie|cookie/gi;
738
+ let match = keyPattern.exec(input);
739
+ while (match) {
740
+ const keyStart = match.index;
741
+ const keyEnd = keyStart + match[0].length;
742
+ if (hasCookieKeyBoundary(input, keyStart, keyEnd)) {
743
+ const quotedKey = parseQuotedKeyContext(input, keyStart, keyEnd);
744
+ if (quotedKey) {
745
+ const afterKey = skipHorizontalWhitespace(input, quotedKey.afterKey);
746
+ if (input[afterKey] === ":") {
747
+ collectCookieMapValueRanges(input, afterKey + 1, ranges);
748
+ match = keyPattern.exec(input);
749
+ continue;
750
+ }
751
+ if (input[afterKey] === ",") {
752
+ const value = parseQuotedString(input, skipHorizontalWhitespace(input, afterKey + 1));
753
+ if (value)
754
+ ranges.push(quotedStringRange(value));
755
+ match = keyPattern.exec(input);
756
+ continue;
757
+ }
758
+ }
759
+ collectPlainCookieHeaderRange(input, keyEnd, ranges);
760
+ }
761
+ match = keyPattern.exec(input);
762
+ }
763
+ return applyReplacementRanges(input, ranges);
764
+ }
765
+ function hasCookieKeyBoundary(input, start, end) {
766
+ return !isCookieKeyCharacter(input[start - 1]) && !isCookieKeyCharacter(input[end]);
767
+ }
768
+ function isCookieKeyCharacter(value) {
769
+ return value !== undefined && /[A-Za-z0-9_-]/.test(value);
770
+ }
771
+ function parseQuotedKeyContext(input, start, end) {
772
+ const before = readQuoteBefore(input, start);
773
+ const after = readQuoteAt(input, end);
774
+ if (!before || !after)
775
+ return null;
776
+ if (before.quote !== after.quote || before.escaped !== after.escaped) {
777
+ return null;
778
+ }
779
+ return { afterKey: end + after.length };
780
+ }
781
+ function collectCookieMapValueRanges(input, start, ranges) {
782
+ const valueStart = skipHorizontalWhitespace(input, start);
783
+ if (input[valueStart] === "[") {
784
+ collectQuotedArrayValueRanges(input, valueStart, ranges);
785
+ return;
786
+ }
787
+ const value = parseQuotedString(input, valueStart);
788
+ if (value)
789
+ ranges.push(quotedStringRange(value));
790
+ }
791
+ function collectQuotedArrayValueRanges(input, start, ranges) {
792
+ let index = start + 1;
793
+ while (index < input.length) {
794
+ index = skipHorizontalWhitespace(input, index);
795
+ if (input[index] === "]" || isLineBreak(input[index]))
796
+ return;
797
+ const value = parseQuotedString(input, index);
798
+ if (value) {
799
+ ranges.push(quotedStringRange(value));
800
+ index = value.end;
801
+ continue;
802
+ }
803
+ index += 1;
804
+ }
805
+ }
806
+ function collectPlainCookieHeaderRange(input, start, ranges) {
807
+ let index = skipHorizontalWhitespace(input, start);
808
+ if (input[index] !== ":" && input[index] !== "=")
809
+ return;
810
+ index = skipHorizontalWhitespace(input, index + 1);
811
+ const end = findLineEnd(input, index);
812
+ if (end > index)
813
+ ranges.push({ start: index, end });
814
+ }
815
+ function parseQuotedString(input, start) {
816
+ const open = readQuoteAt(input, start);
817
+ if (!open)
818
+ return null;
819
+ const contentStart = start + open.length;
820
+ const closeStart = findClosingQuote(input, contentStart, open);
821
+ if (closeStart === null)
822
+ return null;
823
+ return {
824
+ contentStart,
825
+ contentEnd: closeStart,
826
+ end: closeStart + open.length
827
+ };
828
+ }
829
+ function quotedStringRange(value) {
830
+ return { start: value.contentStart, end: value.contentEnd };
831
+ }
832
+ function findClosingQuote(input, start, token) {
833
+ let index = start;
834
+ while (index < input.length) {
835
+ if (isLineBreak(input[index]))
836
+ return null;
837
+ if (token.escaped) {
838
+ if (input[index] === "\\" && input[index + 1] === token.quote) {
839
+ const slashCount = countContiguousBackslashesEndingAt(input, index);
840
+ if (slashCount === 1)
841
+ return index;
842
+ index += 2;
843
+ continue;
844
+ }
845
+ index += 1;
846
+ continue;
847
+ }
848
+ if (input[index] === "\\") {
849
+ index += 2;
850
+ continue;
851
+ }
852
+ if (input[index] === token.quote)
853
+ return index;
854
+ index += 1;
855
+ }
856
+ return null;
857
+ }
858
+ function readQuoteBefore(input, index) {
859
+ const escapedQuote = input.slice(index - 2, index);
860
+ if (escapedQuote === "\\\"" || escapedQuote === "\\'") {
861
+ return { quote: escapedQuote[1], escaped: true, length: 2 };
862
+ }
863
+ const quote = input[index - 1];
864
+ return quote === '"' || quote === "'" ? { quote, escaped: false, length: 1 } : null;
865
+ }
866
+ function readQuoteAt(input, index) {
867
+ const escapedQuote = input.slice(index, index + 2);
868
+ if (escapedQuote === "\\\"" || escapedQuote === "\\'") {
869
+ return { quote: escapedQuote[1], escaped: true, length: 2 };
870
+ }
871
+ const quote = input[index];
872
+ return quote === '"' || quote === "'" ? { quote, escaped: false, length: 1 } : null;
873
+ }
874
+ function skipHorizontalWhitespace(input, start) {
875
+ let index = start;
876
+ while (input[index] === " " || input[index] === "\t")
877
+ index += 1;
878
+ return index;
879
+ }
880
+ function findLineEnd(input, start) {
881
+ let index = start;
882
+ while (index < input.length && !isLineBreak(input[index]))
883
+ index += 1;
884
+ return index;
885
+ }
886
+ function isLineBreak(value) {
887
+ return value === `
888
+ ` || value === "\r";
889
+ }
890
+ function countContiguousBackslashesEndingAt(input, index) {
891
+ let count = 0;
892
+ let cursor = index;
893
+ while (cursor >= 0 && input[cursor] === "\\") {
894
+ count += 1;
895
+ cursor -= 1;
896
+ }
897
+ return count;
898
+ }
899
+ function applyReplacementRanges(input, ranges) {
900
+ const sorted = ranges.filter((range) => range.end > range.start).sort((a, b) => a.start - b.start);
901
+ const deduped = [];
902
+ let lastEnd = -1;
903
+ for (const range of sorted) {
904
+ if (range.start < lastEnd)
905
+ continue;
906
+ deduped.push(range);
907
+ lastEnd = range.end;
908
+ }
909
+ let value = input;
910
+ let replacements = 0;
911
+ for (let index = deduped.length - 1;index >= 0; index -= 1) {
912
+ const range = deduped[index];
913
+ if (value.slice(range.start, range.end) === REDACTED)
914
+ continue;
915
+ value = `${value.slice(0, range.start)}${REDACTED}${value.slice(range.end)}`;
916
+ replacements += 1;
917
+ }
918
+ return { value, replacements };
919
+ }
704
920
  function isSensitiveFlag(value) {
705
921
  const normalized = value.trim().replace(/^-+/, "");
706
922
  if (!normalized || normalized.includes("="))
@@ -732,7 +948,8 @@ function ingestLog(db, entry) {
732
948
  return withEventStoreLock(db, () => ingestLogLocked(db, entry));
733
949
  }
734
950
  function ingestLogLocked(db, entry) {
735
- const eventId = entry.id ?? createEventId();
951
+ const eventIdRedaction = typeof entry.id === "string" ? redactString(entry.id, "id") : null;
952
+ const eventId = entry.id ? eventIdRedaction?.report.applied ? createRedactedEventId(entry.id) : entry.id : createEventId();
736
953
  const existing = db.prepare("SELECT * FROM logs WHERE id = ?").get(eventId);
737
954
  if (existing)
738
955
  return existing;
@@ -750,6 +967,14 @@ function ingestLogLocked(db, entry) {
750
967
  };
751
968
  const redacted = redactLogEntry(normalized);
752
969
  const safeEntry = redacted.value;
970
+ if (eventIdRedaction?.report.applied) {
971
+ const report = mergeRedactionReports(eventIdRedaction.report, redacted.report);
972
+ safeEntry.metadata = {
973
+ ...safeEntry.metadata ?? {},
974
+ redaction: redactionMetadata(report)
975
+ };
976
+ }
977
+ const safeSourceEventId = safeEntry.source_event_id ?? null;
753
978
  const identity = extractIdentity(safeEntry);
754
979
  const envelope = createLogEnvelope(safeEntry, eventId, eventTime, ingestTime, identity);
755
980
  const write = appendRawEvent(db, envelope);
@@ -778,7 +1003,7 @@ function ingestLogLocked(db, entry) {
778
1003
  indexRawEvent(db, {
779
1004
  event_id: eventId,
780
1005
  schema_version: envelope.schema_version,
781
- source_event_id: sourceEventId,
1006
+ source_event_id: safeSourceEventId,
782
1007
  event_type: envelope.type,
783
1008
  event_time: eventTime,
784
1009
  ingest_time: ingestTime,
@@ -918,6 +1143,10 @@ function stringMetadata(metadata, key) {
918
1143
  function createEventId() {
919
1144
  return randomBytes(16).toString("hex");
920
1145
  }
1146
+ function createRedactedEventId(value) {
1147
+ const digest = createHash("sha256").update(value).digest("hex").slice(0, 32);
1148
+ return `log_redacted_${digest}`;
1149
+ }
921
1150
 
922
1151
  // src/lib/package-meta.ts
923
1152
  import { existsSync, readFileSync } from "fs";
@@ -1302,7 +1531,7 @@ function clampNonNegativeInt2(value, fallback) {
1302
1531
  }
1303
1532
 
1304
1533
  // src/lib/universal-ingest.ts
1305
- import { createHash, randomBytes as randomBytes2 } from "crypto";
1534
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
1306
1535
  var UNIVERSAL_EVENT_TYPES = [
1307
1536
  "log",
1308
1537
  "exception",
@@ -1822,7 +2051,7 @@ function normalizeIsoTime(value, field) {
1822
2051
  function deterministicSourceEventId(source, sourceEventId) {
1823
2052
  if (!sourceEventId)
1824
2053
  return;
1825
- const digest = createHash("sha256").update(source).update("\x00").update(sourceEventId).digest("hex").slice(0, 32);
2054
+ const digest = createHash2("sha256").update(source).update("\x00").update(sourceEventId).digest("hex").slice(0, 32);
1826
2055
  return `evt_src_${digest}`;
1827
2056
  }
1828
2057
  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-dbhpykkz.js";
96
+ } from "../index-he072p17.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-89jb7jg9.js";
11
+ } from "../index-a0gz0zzc.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-dbhpykkz.js";
53
+ } from "../index-he072p17.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.29",
3
+ "version": "0.3.31",
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",