@centient/secrets 0.9.0 → 0.11.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.
- package/README.md +424 -8
- package/dist/audit/hmac-chain.d.ts +117 -0
- package/dist/audit/hmac-chain.d.ts.map +1 -0
- package/dist/audit/hmac-chain.js +811 -0
- package/dist/audit/hmac-chain.js.map +1 -0
- package/dist/audit/ocsf.d.ts +108 -0
- package/dist/audit/ocsf.d.ts.map +1 -0
- package/dist/audit/ocsf.js +204 -0
- package/dist/audit/ocsf.js.map +1 -0
- package/dist/cli/hidden-prompt.d.ts.map +1 -1
- package/dist/cli/hidden-prompt.js +55 -10
- package/dist/cli/hidden-prompt.js.map +1 -1
- package/dist/cli/messages.d.ts +40 -8
- package/dist/cli/messages.d.ts.map +1 -1
- package/dist/cli/messages.js +34 -8
- package/dist/cli/messages.js.map +1 -1
- package/dist/cli/secrets-cli.d.ts.map +1 -1
- package/dist/cli/secrets-cli.js +191 -32
- package/dist/cli/secrets-cli.js.map +1 -1
- package/dist/client.d.ts +26 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +129 -0
- package/dist/client.js.map +1 -0
- package/dist/compatibility.d.ts +199 -0
- package/dist/compatibility.d.ts.map +1 -0
- package/dist/compatibility.js +419 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/crypto/darwin-keychain.d.ts +394 -0
- package/dist/crypto/darwin-keychain.d.ts.map +1 -0
- package/dist/crypto/darwin-keychain.js +793 -0
- package/dist/crypto/darwin-keychain.js.map +1 -0
- package/dist/crypto/vault-common.d.ts +55 -12
- package/dist/crypto/vault-common.d.ts.map +1 -1
- package/dist/crypto/vault-common.js +74 -71
- package/dist/crypto/vault-common.js.map +1 -1
- package/dist/environment/EnvironmentManager.d.ts.map +1 -1
- package/dist/environment/EnvironmentManager.js +5 -10
- package/dist/environment/EnvironmentManager.js.map +1 -1
- package/dist/index.d.ts +37 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -6
- package/dist/index.js.map +1 -1
- package/dist/key-providers/keychain-provider.d.ts +30 -2
- package/dist/key-providers/keychain-provider.d.ts.map +1 -1
- package/dist/key-providers/keychain-provider.js +29 -5
- package/dist/key-providers/keychain-provider.js.map +1 -1
- package/dist/key-providers/onepassword-provider.d.ts +65 -5
- package/dist/key-providers/onepassword-provider.d.ts.map +1 -1
- package/dist/key-providers/onepassword-provider.js +159 -67
- package/dist/key-providers/onepassword-provider.js.map +1 -1
- package/dist/key-providers/op-cli.d.ts +78 -0
- package/dist/key-providers/op-cli.d.ts.map +1 -0
- package/dist/key-providers/op-cli.js +123 -0
- package/dist/key-providers/op-cli.js.map +1 -0
- package/dist/key-providers/passphrase-provider.d.ts +20 -0
- package/dist/key-providers/passphrase-provider.d.ts.map +1 -1
- package/dist/key-providers/passphrase-provider.js +49 -0
- package/dist/key-providers/passphrase-provider.js.map +1 -1
- package/dist/key-providers/probe.d.ts +46 -0
- package/dist/key-providers/probe.d.ts.map +1 -0
- package/dist/key-providers/probe.js +70 -0
- package/dist/key-providers/probe.js.map +1 -0
- package/dist/key-providers/types.d.ts +51 -1
- package/dist/key-providers/types.d.ts.map +1 -1
- package/dist/platform/agent-detect.d.ts +39 -3
- package/dist/platform/agent-detect.d.ts.map +1 -1
- package/dist/platform/agent-detect.js +55 -9
- package/dist/platform/agent-detect.js.map +1 -1
- package/dist/platform/session-context.d.ts +77 -0
- package/dist/platform/session-context.d.ts.map +1 -0
- package/dist/platform/session-context.js +83 -0
- package/dist/platform/session-context.js.map +1 -0
- package/dist/policies/audit-trail.d.ts +25 -0
- package/dist/policies/audit-trail.d.ts.map +1 -0
- package/dist/policies/audit-trail.js +31 -0
- package/dist/policies/audit-trail.js.map +1 -0
- package/dist/secret-state.d.ts +54 -0
- package/dist/secret-state.d.ts.map +1 -0
- package/dist/secret-state.js +2 -0
- package/dist/secret-state.js.map +1 -0
- package/dist/sinks/index.d.ts +9 -0
- package/dist/sinks/index.d.ts.map +1 -0
- package/dist/sinks/index.js +5 -0
- package/dist/sinks/index.js.map +1 -0
- package/dist/sinks/jsonl.d.ts +16 -0
- package/dist/sinks/jsonl.d.ts.map +1 -0
- package/dist/sinks/jsonl.js +51 -0
- package/dist/sinks/jsonl.js.map +1 -0
- package/dist/sinks/otel.d.ts +22 -0
- package/dist/sinks/otel.d.ts.map +1 -0
- package/dist/sinks/otel.js +155 -0
- package/dist/sinks/otel.js.map +1 -0
- package/dist/sinks/queue.d.ts +12 -0
- package/dist/sinks/queue.d.ts.map +1 -0
- package/dist/sinks/queue.js +89 -0
- package/dist/sinks/queue.js.map +1 -0
- package/dist/sinks/syslog.d.ts +17 -0
- package/dist/sinks/syslog.d.ts.map +1 -0
- package/dist/sinks/syslog.js +103 -0
- package/dist/sinks/syslog.js.map +1 -0
- package/dist/sinks/types.d.ts +27 -0
- package/dist/sinks/types.d.ts.map +1 -0
- package/dist/sinks/types.js +18 -0
- package/dist/sinks/types.js.map +1 -0
- package/dist/vault/policy.d.ts +64 -34
- package/dist/vault/policy.d.ts.map +1 -1
- package/dist/vault/policy.js +109 -67
- package/dist/vault/policy.js.map +1 -1
- package/dist/vault/session-vault-errors.d.ts +72 -4
- package/dist/vault/session-vault-errors.d.ts.map +1 -1
- package/dist/vault/session-vault-errors.js +95 -4
- package/dist/vault/session-vault-errors.js.map +1 -1
- package/dist/vault/session-vault.d.ts +145 -2
- package/dist/vault/session-vault.d.ts.map +1 -1
- package/dist/vault/session-vault.js +548 -72
- package/dist/vault/session-vault.js.map +1 -1
- package/dist/vault/sidecar.d.ts +9 -0
- package/dist/vault/sidecar.d.ts.map +1 -1
- package/dist/vault/sidecar.js +72 -3
- package/dist/vault/sidecar.js.map +1 -1
- package/dist/vault/types.d.ts +58 -10
- package/dist/vault/types.d.ts.map +1 -1
- package/dist/vault/types.js.map +1 -1
- package/dist/vault/vault-env.d.ts +43 -50
- package/dist/vault/vault-env.d.ts.map +1 -1
- package/dist/vault/vault-env.js +141 -62
- package/dist/vault/vault-env.js.map +1 -1
- package/dist/vault/vault-gpg.d.ts +6 -1
- package/dist/vault/vault-gpg.d.ts.map +1 -1
- package/dist/vault/vault-gpg.js +10 -8
- package/dist/vault/vault-gpg.js.map +1 -1
- package/dist/vault/vault-libsecret.d.ts +6 -1
- package/dist/vault/vault-libsecret.d.ts.map +1 -1
- package/dist/vault/vault-libsecret.js +16 -14
- package/dist/vault/vault-libsecret.js.map +1 -1
- package/dist/vault/vault-onepassword.d.ts +119 -0
- package/dist/vault/vault-onepassword.d.ts.map +1 -0
- package/dist/vault/vault-onepassword.js +271 -0
- package/dist/vault/vault-onepassword.js.map +1 -0
- package/dist/vault/vault-utils.d.ts +37 -3
- package/dist/vault/vault-utils.d.ts.map +1 -1
- package/dist/vault/vault-utils.js +65 -3
- package/dist/vault/vault-utils.js.map +1 -1
- package/dist/vault/vault-windows.d.ts +7 -2
- package/dist/vault/vault-windows.d.ts.map +1 -1
- package/dist/vault/vault-windows.js +21 -16
- package/dist/vault/vault-windows.js.map +1 -1
- package/dist/vault/vault.d.ts +120 -9
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +222 -139
- package/dist/vault/vault.js.map +1 -1
- package/package.json +8 -2
|
@@ -2,21 +2,67 @@
|
|
|
2
2
|
* Agent Environment Detection
|
|
3
3
|
*
|
|
4
4
|
* Detects whether the process is running inside an AI agent session
|
|
5
|
-
* (Claude Code, MCP
|
|
6
|
-
*
|
|
5
|
+
* (Claude Code, MCP subprocess, etc.) by testing the environment markers such
|
|
6
|
+
* a session sets.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* **What this gates (the whole list).** This is the single implementation; the
|
|
9
|
+
* only call sites are:
|
|
10
|
+
*
|
|
11
|
+
* - `cli/secrets-cli.ts` — `runSecrets()` refuses every `centient secrets`
|
|
12
|
+
* subcommand when it returns true, and
|
|
13
|
+
* - `environment/EnvironmentManager.ts` — the environment mutation commands
|
|
14
|
+
* (create / switch / delete-style auth operations).
|
|
15
|
+
*
|
|
16
|
+
* It does **not** gate the programmatic API: `openVault()`, `getCredential()`,
|
|
17
|
+
* `storeCredential()`, and `SessionVault.get()` contain no agent check, and
|
|
18
|
+
* `vault.ts` does not call this function. (An earlier version of this comment
|
|
19
|
+
* claimed it was "shared by vault.ts" — it never was.)
|
|
20
|
+
*
|
|
21
|
+
* **This is a UX guardrail, not a security boundary.** It reads environment
|
|
22
|
+
* variables the calling process itself controls, so any agent can clear them
|
|
23
|
+
* (`env -u CLAUDECODE …`) and walk straight through. Its job is to stop an
|
|
24
|
+
* agent from *accidentally* driving the operator CLI, nothing more. See
|
|
25
|
+
* `docs/threat-model.md` §4.4 and ADR-005 §3, which demotes this check
|
|
26
|
+
* explicitly; repairing the marker names (#189) does not re-promote it.
|
|
27
|
+
*
|
|
28
|
+
* **Marker accuracy matters** precisely because the check is cheap: before
|
|
29
|
+
* #189 it tested `CLAUDE_CODE_ENTRY_POINT` (a misspelling of the real
|
|
30
|
+
* `CLAUDE_CODE_ENTRYPOINT`) and leaned on `CLAUDE_PROJECT_DIR`, which
|
|
31
|
+
* hook-spawned subprocesses get but live interactive sessions do not — so the
|
|
32
|
+
* gate never fired for the sessions it was written for. `tests/agent-detect.test.ts`
|
|
33
|
+
* pins each marker individually so a rename cannot silently disable it again.
|
|
9
34
|
*/
|
|
35
|
+
/**
|
|
36
|
+
* Environment variables whose presence marks an AI agent session.
|
|
37
|
+
*
|
|
38
|
+
* Package-internal (not re-exported from `index.ts`) — exported so tests and
|
|
39
|
+
* the CLI test harnesses clear exactly this set rather than a hand-copied one
|
|
40
|
+
* that can drift.
|
|
41
|
+
*/
|
|
42
|
+
export const AGENT_ENV_MARKERS = [
|
|
43
|
+
/** Set by every live Claude Code session (interactive and headless). */
|
|
44
|
+
"CLAUDECODE",
|
|
45
|
+
/** Set by Claude Code to name how the session was launched (cli, vscode, …). */
|
|
46
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
47
|
+
/** Set in Claude Code hook-spawned subprocesses; absent in live sessions. */
|
|
48
|
+
"CLAUDE_PROJECT_DIR",
|
|
49
|
+
/** Session identifier exposed by some Claude Code integrations. */
|
|
50
|
+
"CLAUDE_CODE_SESSION",
|
|
51
|
+
/** Set by MCP hosts for servers running under a model context. */
|
|
52
|
+
"MCP_CONTEXT",
|
|
53
|
+
/** Set by MCP hosts to name the server being run. */
|
|
54
|
+
"MCP_SERVER_NAME",
|
|
55
|
+
/** Legacy marker retained from the pre-#189 set; harmless if unset. */
|
|
56
|
+
"ANTHROPIC_API_KEY_SOURCE",
|
|
57
|
+
];
|
|
10
58
|
/**
|
|
11
59
|
* Returns true if the current process appears to be running inside an
|
|
12
60
|
* AI agent environment (Claude Code, MCP subprocess, etc.).
|
|
61
|
+
*
|
|
62
|
+
* Advisory only — see the module docstring. A false result does not mean "a
|
|
63
|
+
* human is at the keyboard", it means "no marker was found".
|
|
13
64
|
*/
|
|
14
65
|
export function isAgentEnvironment() {
|
|
15
|
-
return !!
|
|
16
|
-
process.env["MCP_CONTEXT"] ||
|
|
17
|
-
process.env["CLAUDE_CODE_SESSION"] ||
|
|
18
|
-
process.env["CLAUDE_CODE_ENTRY_POINT"] ||
|
|
19
|
-
process.env["ANTHROPIC_API_KEY_SOURCE"] ||
|
|
20
|
-
process.env["MCP_SERVER_NAME"]);
|
|
66
|
+
return AGENT_ENV_MARKERS.some((name) => !!process.env[name]);
|
|
21
67
|
}
|
|
22
68
|
//# sourceMappingURL=agent-detect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-detect.js","sourceRoot":"","sources":["../../src/platform/agent-detect.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agent-detect.js","sourceRoot":"","sources":["../../src/platform/agent-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,wEAAwE;IACxE,YAAY;IACZ,gFAAgF;IAChF,wBAAwB;IACxB,6EAA6E;IAC7E,oBAAoB;IACpB,mEAAmE;IACnE,qBAAqB;IACrB,kEAAkE;IAClE,aAAa;IACb,qDAAqD;IACrD,iBAAiB;IACjB,uEAAuE;IACvE,0BAA0B;CAClB,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-context detection (#188).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is not platform detection
|
|
5
|
+
*
|
|
6
|
+
* The keychain accessors in this package used to branch on nothing at all:
|
|
7
|
+
* one code path, which implicitly assumed an interactive GUI session. That
|
|
8
|
+
* assumption is wrong in exactly the contexts a credential store matters most
|
|
9
|
+
* — a launchd system-domain daemon and an SSH login both run **without a
|
|
10
|
+
* security session** attached to the user's login keychain:
|
|
11
|
+
*
|
|
12
|
+
* - no consent UI can render, so an item whose ACL is not preauthorized at
|
|
13
|
+
* write time is unreadable and there is nothing to click;
|
|
14
|
+
* - the keychain **search list** cannot be trusted, because it leads with
|
|
15
|
+
* the (locked) login keychain, whose copy of an item shadows a perfectly
|
|
16
|
+
* readable System-keychain copy.
|
|
17
|
+
*
|
|
18
|
+
* `process.platform === "darwin"` answers neither question — a Mac is a Mac in
|
|
19
|
+
* all three contexts. So the branch that matters is *session vs sessionless*,
|
|
20
|
+
* and that is what this module reports.
|
|
21
|
+
*
|
|
22
|
+
* ## The signals, and their limits
|
|
23
|
+
*
|
|
24
|
+
* There is no supported API that says "you are in the launchd system domain",
|
|
25
|
+
* so this is a heuristic and is documented as one. It is deliberately
|
|
26
|
+
* conservative, and the read path is built so that a misclassification is
|
|
27
|
+
* harmless rather than fatal: a sessionless read consults the System keychain
|
|
28
|
+
* *first* and only then falls back to the search list, so classifying an
|
|
29
|
+
* interactive session as sessionless can never lose a credential that was
|
|
30
|
+
* previously readable (see `readKeychainCredential` in
|
|
31
|
+
* `../crypto/darwin-keychain.ts`).
|
|
32
|
+
*
|
|
33
|
+
* The override exists for the case the heuristic cannot see — a daemon whose
|
|
34
|
+
* environment is scrubbed, or a test rig — and it wins over every signal.
|
|
35
|
+
*/
|
|
36
|
+
/** Environment variable that forces the answer. `sessionless` | `interactive`. */
|
|
37
|
+
export declare const SESSION_CONTEXT_OVERRIDE_ENV = "CENTIENT_SECRETS_SESSION_CONTEXT";
|
|
38
|
+
/** Injectable seam. Both fields default to the live process. */
|
|
39
|
+
export interface SessionContextDeps {
|
|
40
|
+
platform: NodeJS.Platform;
|
|
41
|
+
env: NodeJS.ProcessEnv;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Why a context was classified the way it was. Carried so a diagnostic can
|
|
45
|
+
* explain the read path it is about to take instead of asserting it.
|
|
46
|
+
*/
|
|
47
|
+
export type SessionContext = {
|
|
48
|
+
kind: "sessionless";
|
|
49
|
+
/**
|
|
50
|
+
* - `ssh` — an SSH login: `SSH_CONNECTION`/`SSH_TTY`/`SSH_CLIENT` set.
|
|
51
|
+
* - `launchd-daemon` — a launchd-managed job with no controlling
|
|
52
|
+
* terminal (`XPC_SERVICE_NAME` set to a real job label, `TERM` unset).
|
|
53
|
+
* - `forced` — the override said so.
|
|
54
|
+
*/
|
|
55
|
+
reason: "ssh" | "launchd-daemon" | "forced";
|
|
56
|
+
} | {
|
|
57
|
+
kind: "interactive";
|
|
58
|
+
/**
|
|
59
|
+
* - `session` — no sessionless signal was present.
|
|
60
|
+
* - `not-darwin` — the distinction is macOS-specific; elsewhere there is
|
|
61
|
+
* no keychain search list to mistrust.
|
|
62
|
+
* - `forced` — the override said so.
|
|
63
|
+
*/
|
|
64
|
+
reason: "session" | "not-darwin" | "forced";
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Classify the current execution context.
|
|
68
|
+
*
|
|
69
|
+
* Order of precedence: explicit override, then SSH, then launchd-without-a-tty,
|
|
70
|
+
* then interactive. Off darwin the answer is always `interactive` with reason
|
|
71
|
+
* `not-darwin` — the search-list shadowing problem is a macOS keychain problem,
|
|
72
|
+
* and other backends have no equivalent notion.
|
|
73
|
+
*/
|
|
74
|
+
export declare function detectSessionContext(deps?: Partial<SessionContextDeps>): SessionContext;
|
|
75
|
+
/** Convenience predicate over {@link detectSessionContext}. */
|
|
76
|
+
export declare function isSessionlessContext(deps?: Partial<SessionContextDeps>): boolean;
|
|
77
|
+
//# sourceMappingURL=session-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../src/platform/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,qCAAqC,CAAC;AAE/E,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;OAKG;IACH,MAAM,EAAE,KAAK,GAAG,gBAAgB,GAAG,QAAQ,CAAC;CAC7C,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;OAKG;IACH,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;CAC7C,CAAC;AAaN;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,OAAO,CAAC,kBAAkB,CAAM,GACrC,cAAc,CAyBhB;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,OAAO,CAAC,kBAAkB,CAAM,GACrC,OAAO,CAET"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-context detection (#188).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is not platform detection
|
|
5
|
+
*
|
|
6
|
+
* The keychain accessors in this package used to branch on nothing at all:
|
|
7
|
+
* one code path, which implicitly assumed an interactive GUI session. That
|
|
8
|
+
* assumption is wrong in exactly the contexts a credential store matters most
|
|
9
|
+
* — a launchd system-domain daemon and an SSH login both run **without a
|
|
10
|
+
* security session** attached to the user's login keychain:
|
|
11
|
+
*
|
|
12
|
+
* - no consent UI can render, so an item whose ACL is not preauthorized at
|
|
13
|
+
* write time is unreadable and there is nothing to click;
|
|
14
|
+
* - the keychain **search list** cannot be trusted, because it leads with
|
|
15
|
+
* the (locked) login keychain, whose copy of an item shadows a perfectly
|
|
16
|
+
* readable System-keychain copy.
|
|
17
|
+
*
|
|
18
|
+
* `process.platform === "darwin"` answers neither question — a Mac is a Mac in
|
|
19
|
+
* all three contexts. So the branch that matters is *session vs sessionless*,
|
|
20
|
+
* and that is what this module reports.
|
|
21
|
+
*
|
|
22
|
+
* ## The signals, and their limits
|
|
23
|
+
*
|
|
24
|
+
* There is no supported API that says "you are in the launchd system domain",
|
|
25
|
+
* so this is a heuristic and is documented as one. It is deliberately
|
|
26
|
+
* conservative, and the read path is built so that a misclassification is
|
|
27
|
+
* harmless rather than fatal: a sessionless read consults the System keychain
|
|
28
|
+
* *first* and only then falls back to the search list, so classifying an
|
|
29
|
+
* interactive session as sessionless can never lose a credential that was
|
|
30
|
+
* previously readable (see `readKeychainCredential` in
|
|
31
|
+
* `../crypto/darwin-keychain.ts`).
|
|
32
|
+
*
|
|
33
|
+
* The override exists for the case the heuristic cannot see — a daemon whose
|
|
34
|
+
* environment is scrubbed, or a test rig — and it wins over every signal.
|
|
35
|
+
*/
|
|
36
|
+
/** Environment variable that forces the answer. `sessionless` | `interactive`. */
|
|
37
|
+
export const SESSION_CONTEXT_OVERRIDE_ENV = "CENTIENT_SECRETS_SESSION_CONTEXT";
|
|
38
|
+
function resolve(deps) {
|
|
39
|
+
return {
|
|
40
|
+
platform: deps.platform ?? process.platform,
|
|
41
|
+
env: deps.env ?? process.env,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function isSet(value) {
|
|
45
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Classify the current execution context.
|
|
49
|
+
*
|
|
50
|
+
* Order of precedence: explicit override, then SSH, then launchd-without-a-tty,
|
|
51
|
+
* then interactive. Off darwin the answer is always `interactive` with reason
|
|
52
|
+
* `not-darwin` — the search-list shadowing problem is a macOS keychain problem,
|
|
53
|
+
* and other backends have no equivalent notion.
|
|
54
|
+
*/
|
|
55
|
+
export function detectSessionContext(deps = {}) {
|
|
56
|
+
const { platform, env } = resolve(deps);
|
|
57
|
+
const override = env[SESSION_CONTEXT_OVERRIDE_ENV]?.trim();
|
|
58
|
+
if (override === "sessionless")
|
|
59
|
+
return { kind: "sessionless", reason: "forced" };
|
|
60
|
+
if (override === "interactive")
|
|
61
|
+
return { kind: "interactive", reason: "forced" };
|
|
62
|
+
if (platform !== "darwin")
|
|
63
|
+
return { kind: "interactive", reason: "not-darwin" };
|
|
64
|
+
if (isSet(env["SSH_CONNECTION"]) || isSet(env["SSH_TTY"]) || isSet(env["SSH_CLIENT"])) {
|
|
65
|
+
return { kind: "sessionless", reason: "ssh" };
|
|
66
|
+
}
|
|
67
|
+
// launchd exports `XPC_SERVICE_NAME` as the job label for a managed job; a
|
|
68
|
+
// process started from a shell either has it unset or set to the literal
|
|
69
|
+
// `"0"`. A managed job that also has no `TERM` has no controlling terminal
|
|
70
|
+
// and therefore no way to render a consent prompt. Both halves are required:
|
|
71
|
+
// a GUI application is also a launchd job, and demoting one of those to
|
|
72
|
+
// "sessionless" would be a needless change of read path.
|
|
73
|
+
const xpc = env["XPC_SERVICE_NAME"];
|
|
74
|
+
if (isSet(xpc) && xpc?.trim() !== "0" && !isSet(env["TERM"])) {
|
|
75
|
+
return { kind: "sessionless", reason: "launchd-daemon" };
|
|
76
|
+
}
|
|
77
|
+
return { kind: "interactive", reason: "session" };
|
|
78
|
+
}
|
|
79
|
+
/** Convenience predicate over {@link detectSessionContext}. */
|
|
80
|
+
export function isSessionlessContext(deps = {}) {
|
|
81
|
+
return detectSessionContext(deps).kind === "sessionless";
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=session-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-context.js","sourceRoot":"","sources":["../../src/platform/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAkC/E,SAAS,OAAO,CAAC,IAAiC;IAChD,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ;QAC3C,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;KAC7B,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,KAAyB;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAoC,EAAE;IAEtC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,MAAM,QAAQ,GAAG,GAAG,CAAC,4BAA4B,CAAC,EAAE,IAAI,EAAE,CAAC;IAC3D,IAAI,QAAQ,KAAK,aAAa;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACjF,IAAI,QAAQ,KAAK,aAAa;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAEjF,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAEhF,IAAI,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QACtF,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAChD,CAAC;IAED,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,6EAA6E;IAC7E,wEAAwE;IACxE,yDAAyD;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACpC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3D,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACpD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,oBAAoB,CAClC,OAAoC,EAAE;IAEtC,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CredentialAuditEvent } from "../audit/ocsf.js";
|
|
2
|
+
import type { AuditSink } from "../sinks/types.js";
|
|
3
|
+
import type { SecretsPolicy } from "../vault/policy.js";
|
|
4
|
+
/** Structural seam implemented by `createHmacAuditChain()`. */
|
|
5
|
+
export interface AuditEventChain {
|
|
6
|
+
append(event: CredentialAuditEvent): CredentialAuditEvent;
|
|
7
|
+
}
|
|
8
|
+
export interface AuditTrailOptions {
|
|
9
|
+
/** Defaults to the application-owned global OpenTelemetry tracer. */
|
|
10
|
+
readonly sink?: AuditSink;
|
|
11
|
+
/** Successful and missing reads are included unless explicitly disabled. */
|
|
12
|
+
readonly includeReads?: boolean;
|
|
13
|
+
/** Optional synchronous integrity chain applied before the record reaches a sink. */
|
|
14
|
+
readonly chain?: AuditEventChain;
|
|
15
|
+
/** Actor used when the operation did not carry an explicit caller. */
|
|
16
|
+
readonly defaultActor?: string;
|
|
17
|
+
/** Injectable record UID seam. */
|
|
18
|
+
readonly uid?: () => string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Map value-free policy outcomes to OCSF, optionally chain them, then emit to
|
|
22
|
+
* one synchronous sink boundary. Rejected and failed reads are never filtered.
|
|
23
|
+
*/
|
|
24
|
+
export declare function auditTrail(options?: AuditTrailOptions): SecretsPolicy;
|
|
25
|
+
//# sourceMappingURL=audit-trail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-trail.d.ts","sourceRoot":"","sources":["../../src/policies/audit-trail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEtE,+DAA+D;AAC/D,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;CAC3D;AAED,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,sEAAsE;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,kCAAkC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,aAAa,CA2BzE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { toCredentialAuditEvent } from "../audit/ocsf.js";
|
|
2
|
+
import { otelAuditSink } from "../sinks/otel.js";
|
|
3
|
+
/**
|
|
4
|
+
* Map value-free policy outcomes to OCSF, optionally chain them, then emit to
|
|
5
|
+
* one synchronous sink boundary. Rejected and failed reads are never filtered.
|
|
6
|
+
*/
|
|
7
|
+
export function auditTrail(options = {}) {
|
|
8
|
+
const includeReads = options.includeReads ?? true;
|
|
9
|
+
const sink = options.sink ?? otelAuditSink();
|
|
10
|
+
const chain = options.chain;
|
|
11
|
+
const appendToChain = chain?.append.bind(chain);
|
|
12
|
+
const defaultActor = options.defaultActor;
|
|
13
|
+
const uid = options.uid;
|
|
14
|
+
return {
|
|
15
|
+
name: "auditTrail",
|
|
16
|
+
observesPolicyRejections: true,
|
|
17
|
+
after(event) {
|
|
18
|
+
if (!includeReads &&
|
|
19
|
+
(event.type === "credential_read" ||
|
|
20
|
+
event.type === "credential_read_missing")) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const projected = toCredentialAuditEvent(event, {
|
|
24
|
+
...(defaultActor !== undefined ? { defaultActor } : {}),
|
|
25
|
+
...(uid !== undefined ? { uid } : {}),
|
|
26
|
+
});
|
|
27
|
+
sink(appendToChain?.(projected) ?? projected);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=audit-trail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-trail.js","sourceRoot":"","sources":["../../src/policies/audit-trail.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsBjD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAA6B,EAAE;IACxD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,EAAE,CAAC;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,MAAM,aAAa,GAAG,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAExB,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,wBAAwB,EAAE,IAAI;QAC9B,KAAK,CAAC,KAAmB;YACvB,IACE,CAAC,YAAY;gBACb,CAAC,KAAK,CAAC,IAAI,KAAK,iBAAiB;oBAC/B,KAAK,CAAC,IAAI,KAAK,yBAAyB,CAAC,EAC3C,CAAC;gBACD,OAAO;YACT,CAAC;YAED,MAAM,SAAS,GAAG,sBAAsB,CAAC,KAAK,EAAE;gBAC9C,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvD,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtC,CAAC,CAAC;YACH,IAAI,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The five-way credential state (#188).
|
|
3
|
+
*
|
|
4
|
+
* Every probe in this package answers with this shape, at both credential
|
|
5
|
+
* layers — the `KeyProvider` layer (the vault master key) and the
|
|
6
|
+
* `VaultBackend` cascade (credential values).
|
|
7
|
+
*
|
|
8
|
+
* ## Why five kinds and not a boolean
|
|
9
|
+
*
|
|
10
|
+
* The defect this type exists to kill is the collapse of "there is no such
|
|
11
|
+
* credential" into "I could not read the credential". They have opposite
|
|
12
|
+
* remedies — one says *store it*, the other says *unlock the store / fix the
|
|
13
|
+
* ACL / run me in a context that has a session* — and a caller handed a single
|
|
14
|
+
* `null` cannot tell them apart. A backend that reports `absent` for an item
|
|
15
|
+
* it simply could not open turns a context change into a silent outage: the
|
|
16
|
+
* reader logs "none configured" and everything downstream behaves as though
|
|
17
|
+
* the operator never set it up.
|
|
18
|
+
*
|
|
19
|
+
* So the kinds are deliberately irreducible:
|
|
20
|
+
*
|
|
21
|
+
* - `found` — the credential is there. **Value-free**: a probe never carries
|
|
22
|
+
* secret material, so this variant has no `value` field. Reading the bytes
|
|
23
|
+
* is a separate, explicit call.
|
|
24
|
+
* - `absent` — the store answered, and there is no such item.
|
|
25
|
+
* - `locked` — the item exists but this execution context cannot open it
|
|
26
|
+
* (locked keychain, an ACL that does not preauthorize the reader, no
|
|
27
|
+
* session in which a consent prompt could render).
|
|
28
|
+
* - `not-applicable` — this store cannot exist here at all (a macOS Keychain
|
|
29
|
+
* probe on Linux). Distinct from `failed`: nothing went wrong.
|
|
30
|
+
* - `failed` — the store was contacted and did not give a usable answer.
|
|
31
|
+
* Includes the corrupt case where a successful read yields zero bytes: an
|
|
32
|
+
* empty credential is never reported as `found`.
|
|
33
|
+
*
|
|
34
|
+
* `not-applicable` and `failed` carry a value-free human-readable string for
|
|
35
|
+
* diagnostics. No variant ever carries a credential.
|
|
36
|
+
*
|
|
37
|
+
* See issue #188, ADR-005, and `docs/threat-model.md`.
|
|
38
|
+
*/
|
|
39
|
+
export type SecretState = {
|
|
40
|
+
kind: "found";
|
|
41
|
+
} | {
|
|
42
|
+
kind: "absent";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "locked";
|
|
45
|
+
} | {
|
|
46
|
+
kind: "not-applicable";
|
|
47
|
+
reason: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "failed";
|
|
50
|
+
detail: string;
|
|
51
|
+
};
|
|
52
|
+
/** The discriminant of {@link SecretState}, for exhaustive switches. */
|
|
53
|
+
export type SecretStateKind = SecretState["kind"];
|
|
54
|
+
//# sourceMappingURL=secret-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-state.d.ts","sourceRoot":"","sources":["../src/secret-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-state.js","sourceRoot":"","sources":["../src/secret-state.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { jsonlAuditSink } from "./jsonl.js";
|
|
2
|
+
export type { JsonlAuditSinkOptions } from "./jsonl.js";
|
|
3
|
+
export { credentialAuditSpanAttributes, otelAuditSink, OtelAuditSinkDegradedError, } from "./otel.js";
|
|
4
|
+
export type { OtelAuditSinkOptions } from "./otel.js";
|
|
5
|
+
export { syslogAuditSink } from "./syslog.js";
|
|
6
|
+
export type { SyslogAuditSender, SyslogAuditSinkOptions, } from "./syslog.js";
|
|
7
|
+
export { AuditSinkOverflowError } from "./types.js";
|
|
8
|
+
export type { AuditSink, AuditSinkErrorHandler, AuditSinkHealth, } from "./types.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sinks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EACL,6BAA6B,EAC7B,aAAa,EACb,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACV,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EACV,SAAS,EACT,qBAAqB,EACrB,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { jsonlAuditSink } from "./jsonl.js";
|
|
2
|
+
export { credentialAuditSpanAttributes, otelAuditSink, OtelAuditSinkDegradedError, } from "./otel.js";
|
|
3
|
+
export { syslogAuditSink } from "./syslog.js";
|
|
4
|
+
export { AuditSinkOverflowError } from "./types.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sinks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EACL,6BAA6B,EAC7B,aAAa,EACb,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AuditSink, AuditSinkErrorHandler } from "./types.js";
|
|
2
|
+
export interface JsonlAuditSinkOptions {
|
|
3
|
+
readonly filePath: string;
|
|
4
|
+
/** Creation mode for a new log file. Defaults to owner-only `0o600`. */
|
|
5
|
+
readonly fileMode?: number;
|
|
6
|
+
/** Creation mode for missing directories. Defaults to owner-only `0o700`. */
|
|
7
|
+
readonly directoryMode?: number;
|
|
8
|
+
readonly maxPending?: number;
|
|
9
|
+
readonly onError?: AuditSinkErrorHandler;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Append bare OCSF records as newline-delimited JSON. Writes are serialized in
|
|
13
|
+
* emission order; `flush()` rejects if any queued append failed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function jsonlAuditSink(options: JsonlAuditSinkOptions): AuditSink;
|
|
16
|
+
//# sourceMappingURL=jsonl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../../src/sinks/jsonl.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGnE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;CAC1C;AAQD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,CAuCxE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { createQueuedAuditSink } from "./queue.js";
|
|
4
|
+
function assertMode(mode, field) {
|
|
5
|
+
if (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {
|
|
6
|
+
throw new RangeError(`${field} must be an integer permission mode from 0o000 to 0o777`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Append bare OCSF records as newline-delimited JSON. Writes are serialized in
|
|
11
|
+
* emission order; `flush()` rejects if any queued append failed.
|
|
12
|
+
*/
|
|
13
|
+
export function jsonlAuditSink(options) {
|
|
14
|
+
if (options.filePath.length === 0) {
|
|
15
|
+
throw new TypeError("filePath must not be empty");
|
|
16
|
+
}
|
|
17
|
+
const fileMode = options.fileMode ?? 0o600;
|
|
18
|
+
const directoryMode = options.directoryMode ?? 0o700;
|
|
19
|
+
assertMode(fileMode, "fileMode");
|
|
20
|
+
assertMode(directoryMode, "directoryMode");
|
|
21
|
+
let directoryReady = false;
|
|
22
|
+
return createQueuedAuditSink({
|
|
23
|
+
name: "jsonl",
|
|
24
|
+
...(options.maxPending !== undefined ? { maxPending: options.maxPending } : {}),
|
|
25
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
26
|
+
async write(event) {
|
|
27
|
+
if (!directoryReady) {
|
|
28
|
+
await mkdir(dirname(options.filePath), {
|
|
29
|
+
recursive: true,
|
|
30
|
+
mode: directoryMode,
|
|
31
|
+
});
|
|
32
|
+
directoryReady = true;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
await appendFile(options.filePath, `${JSON.stringify(event)}\n`, {
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
mode: fileMode,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof Error &&
|
|
42
|
+
"code" in error &&
|
|
43
|
+
error.code === "ENOENT") {
|
|
44
|
+
directoryReady = false;
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../src/sinks/jsonl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAYnD,SAAS,UAAU,CAAC,IAAY,EAAE,KAAa;IAC7C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,UAAU,CAAC,GAAG,KAAK,yDAAyD,CAAC,CAAC;IAC1F,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;IAC3C,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC;IACrD,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACjC,UAAU,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IAE3C,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,OAAO,qBAAqB,CAAC;QAC3B,IAAI,EAAE,OAAO;QACb,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/E,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,KAAK,CAAC,KAAK,CAAC,KAAK;YACf,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;oBACrC,SAAS,EAAE,IAAI;oBACf,IAAI,EAAE,aAAa;iBACpB,CAAC,CAAC;gBACH,cAAc,GAAG,IAAI,CAAC;YACxB,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC/D,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,QAAQ;iBACf,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IACE,KAAK,YAAY,KAAK;oBACtB,MAAM,IAAI,KAAK;oBACd,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAClD,CAAC;oBACD,cAAc,GAAG,KAAK,CAAC;gBACzB,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Attributes, type Tracer } from "@opentelemetry/api";
|
|
2
|
+
import type { CredentialAuditEvent } from "../audit/ocsf.js";
|
|
3
|
+
import { type AuditSink, type AuditSinkErrorHandler } from "./types.js";
|
|
4
|
+
export declare class OtelAuditSinkDegradedError extends Error {
|
|
5
|
+
readonly code: "OTEL_AUDIT_SINK_DEGRADED";
|
|
6
|
+
constructor(reason: "not-recording" | "not-sampled");
|
|
7
|
+
}
|
|
8
|
+
export interface OtelAuditSinkOptions {
|
|
9
|
+
/** Application-owned tracer; defaults to the global OTel API tracer. */
|
|
10
|
+
readonly tracer?: Tracer;
|
|
11
|
+
/** Called once when spans are not recording/sampled, and on synchronous errors. */
|
|
12
|
+
readonly onError?: AuditSinkErrorHandler;
|
|
13
|
+
}
|
|
14
|
+
/** Flatten one OCSF JSON record into primitive span attributes. */
|
|
15
|
+
export declare function credentialAuditSpanAttributes(event: CredentialAuditEvent): Attributes;
|
|
16
|
+
/**
|
|
17
|
+
* Emit one completed credential operation as a retroactive OTel span. The
|
|
18
|
+
* application owns SDK/exporter configuration; this package depends only on
|
|
19
|
+
* the OTel API, as required for reusable instrumentation libraries.
|
|
20
|
+
*/
|
|
21
|
+
export declare function otelAuditSink(options?: OtelAuditSinkOptions): AuditSink;
|
|
22
|
+
//# sourceMappingURL=otel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../../src/sinks/otel.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,MAAM,EACZ,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,qBAAqB,EAE3B,MAAM,YAAY,CAAC;AAKpB,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,EAAG,0BAA0B,CAAU;gBAExC,MAAM,EAAE,eAAe,GAAG,aAAa;CAQpD;AAED,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;CAC1C;AAED,mEAAmE;AACnE,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oBAAoB,GAC1B,UAAU,CA6DZ;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,SAAS,CA0E3E"}
|