@ouro.bot/cli 0.1.0-alpha.286 → 0.1.0-alpha.287
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/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.287",
|
|
6
|
+
"changes": [
|
|
7
|
+
"feat(nerves): add two-layer log redaction to the NDJSON sink. Structured key-based redaction strips sensitive fields (passwords, tokens, API keys, auth headers) from event meta objects before serialization. Regex fallback catches secrets in serialized strings that bypass structured checks (Anthropic keys, OpenAI keys, Bearer tokens, URL token params). Redacted values are replaced with `[REDACTED:key_name]` markers that preserve debugging context without exposing secrets. Redaction happens at the sink level only — in-memory events retain full data for runtime use. `OURO_LOG_VERBOSE=1` env var disables redaction for active debugging sessions. New `src/nerves/redact.ts` module with 38 tests across 3 test files including a 10-case golden corpus covering nested objects, mixed safe/secret fields, and realistic key patterns."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.286",
|
|
6
12
|
"changes": [
|
|
@@ -69,6 +69,8 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
69
69
|
"mind/embedding-provider",
|
|
70
70
|
// Commerce utility module: error classes and pure helpers (no independent side effects).
|
|
71
71
|
"repertoire/commerce-errors",
|
|
72
|
+
// Log redaction: pure utility consumed by the NDJSON sink (no independent side effects).
|
|
73
|
+
"nerves/redact",
|
|
72
74
|
];
|
|
73
75
|
function isDispatchExempt(filePath) {
|
|
74
76
|
return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));
|
package/dist/nerves/index.js
CHANGED
|
@@ -54,6 +54,7 @@ const zlib = __importStar(require("zlib"));
|
|
|
54
54
|
// lazily inside `getRuntimeLogger()`, and we only call `emitNervesEvent` from
|
|
55
55
|
// inside `rotateIfNeeded` — never at module top level.
|
|
56
56
|
const runtime_1 = require("./runtime");
|
|
57
|
+
const redact_1 = require("./redact");
|
|
57
58
|
const LEVEL_PRIORITY = {
|
|
58
59
|
debug: 10,
|
|
59
60
|
info: 20,
|
|
@@ -293,6 +294,7 @@ function createNdjsonFileSink(filePath, optionsOrMaxSize) {
|
|
|
293
294
|
? { maxSizeBytes: optionsOrMaxSize }
|
|
294
295
|
: optionsOrMaxSize ?? {};
|
|
295
296
|
const rotationCheckInterval = options.rotationCheckIntervalBytes ?? DEFAULT_ROTATION_CHECK_INTERVAL_BYTES;
|
|
297
|
+
const verbose = process.env.OURO_LOG_VERBOSE === "1";
|
|
296
298
|
const queue = [];
|
|
297
299
|
let flushing = false;
|
|
298
300
|
let bytesSinceCheck = 0;
|
|
@@ -316,7 +318,9 @@ function createNdjsonFileSink(filePath, optionsOrMaxSize) {
|
|
|
316
318
|
});
|
|
317
319
|
}
|
|
318
320
|
return (entry) => {
|
|
319
|
-
const line =
|
|
321
|
+
const line = verbose
|
|
322
|
+
? `${JSON.stringify(entry)}\n`
|
|
323
|
+
: `${(0, redact_1.redactString)(JSON.stringify((0, redact_1.redactLogEntry)(entry)))}\n`;
|
|
320
324
|
bytesSinceCheck += line.length;
|
|
321
325
|
queue.push(line);
|
|
322
326
|
flush();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SENSITIVE_PATTERNS = exports.SENSITIVE_KEYS = void 0;
|
|
4
|
+
exports.redactMeta = redactMeta;
|
|
5
|
+
exports.redactString = redactString;
|
|
6
|
+
exports.redactLogEntry = redactLogEntry;
|
|
7
|
+
/**
|
|
8
|
+
* Lowercase key names that trigger structured redaction.
|
|
9
|
+
* Matched case-insensitively against meta object keys.
|
|
10
|
+
*/
|
|
11
|
+
exports.SENSITIVE_KEYS = new Set([
|
|
12
|
+
"password",
|
|
13
|
+
"secret",
|
|
14
|
+
"token",
|
|
15
|
+
"accesstoken",
|
|
16
|
+
"refreshtoken",
|
|
17
|
+
"clientsecret",
|
|
18
|
+
"apikey",
|
|
19
|
+
"oauthaccesstoken",
|
|
20
|
+
"authorization",
|
|
21
|
+
"cookie",
|
|
22
|
+
"set-cookie",
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Regex patterns for string-level fallback redaction.
|
|
26
|
+
* Applied in order; each match is replaced with `[REDACTED:name]`.
|
|
27
|
+
*/
|
|
28
|
+
exports.SENSITIVE_PATTERNS = [
|
|
29
|
+
{ name: "anthropic_key", pattern: /sk-ant-[A-Za-z0-9_-]+/g },
|
|
30
|
+
{ name: "openai_key", pattern: /sk-proj-[A-Za-z0-9_-]+/g },
|
|
31
|
+
{ name: "bearer_token", pattern: /Bearer [A-Za-z0-9\-_.]+/g },
|
|
32
|
+
{ name: "api_key_assignment", pattern: /api[_-]?key[=:]\s*["']?[A-Za-z0-9\-_.]+/g },
|
|
33
|
+
{ name: "url_token", pattern: /[?&](?:token|key|api_key|access_token)=[A-Za-z0-9\-_.%]+/g },
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Deep-clone meta and replace values at sensitive keys with `[REDACTED:key_name]`.
|
|
37
|
+
* Recurses into nested objects. Case-insensitive key matching.
|
|
38
|
+
*/
|
|
39
|
+
function redactMeta(meta) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const key of Object.keys(meta)) {
|
|
42
|
+
const value = meta[key];
|
|
43
|
+
if (exports.SENSITIVE_KEYS.has(key.toLowerCase())) {
|
|
44
|
+
result[key] = `[REDACTED:${key}]`;
|
|
45
|
+
}
|
|
46
|
+
else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
47
|
+
result[key] = redactMeta(value);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
result[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Apply all regex patterns to a string, replacing matches with `[REDACTED:pattern_name]`.
|
|
57
|
+
*/
|
|
58
|
+
function redactString(text) {
|
|
59
|
+
let result = text;
|
|
60
|
+
for (const { name, pattern } of exports.SENSITIVE_PATTERNS) {
|
|
61
|
+
// Reset lastIndex for global regexes reused across calls
|
|
62
|
+
pattern.lastIndex = 0;
|
|
63
|
+
result = result.replace(pattern, `[REDACTED:${name}]`);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Full redaction pipeline: redact meta (structured keys), then redact message (regex patterns).
|
|
69
|
+
* Returns a new LogEvent -- never mutates the input.
|
|
70
|
+
*/
|
|
71
|
+
function redactLogEntry(entry) {
|
|
72
|
+
const redactedMeta = redactMeta(entry.meta);
|
|
73
|
+
const redactedMessage = redactString(entry.message);
|
|
74
|
+
return {
|
|
75
|
+
...entry,
|
|
76
|
+
meta: redactedMeta,
|
|
77
|
+
message: redactedMessage,
|
|
78
|
+
};
|
|
79
|
+
}
|