@rohirik/openltm-core 2.8.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 +67 -0
- package/assets/opencode/agents/aegis.md +211 -0
- package/assets/opencode/plugins/aegis.ts +3 -0
- package/assets/opencode/skills/AgentTrustBoundaries/ContextCrushDefense.md +104 -0
- package/assets/opencode/skills/AgentTrustBoundaries/SKILL.md +31 -0
- package/assets/opencode/skills/AgentTrustBoundaries/TrustBoundaryPatterns.md +114 -0
- package/assets/opencode/skills/AgentTrustBoundaries/Workflows/DefendContextCrush.md +27 -0
- package/assets/opencode/skills/AgentTrustBoundaries/Workflows/HandleUntrustedContent.md +27 -0
- package/assets/opencode/skills/CommandPathSafety/CommandInjectionPatterns.md +95 -0
- package/assets/opencode/skills/CommandPathSafety/PathTraversalAndInstallerSafety.md +106 -0
- package/assets/opencode/skills/CommandPathSafety/SKILL.md +31 -0
- package/assets/opencode/skills/CommandPathSafety/Workflows/EnforcePathBoundaries.md +27 -0
- package/assets/opencode/skills/CommandPathSafety/Workflows/HardenCommandExecution.md +27 -0
- package/assets/opencode/skills/SecretSafeHandling/CloudCredentialPatterns.md +106 -0
- package/assets/opencode/skills/SecretSafeHandling/SKILL.md +31 -0
- package/assets/opencode/skills/SecretSafeHandling/SecretHandlingPlaybook.md +102 -0
- package/assets/opencode/skills/SecretSafeHandling/Workflows/DesignSecretSafeFlow.md +27 -0
- package/assets/opencode/skills/SecretSafeHandling/Workflows/RemoveSecretExposure.md +27 -0
- package/package.json +41 -0
- package/src/__tests__/cli/claude.test.ts +122 -0
- package/src/__tests__/cli/detect.test.ts +91 -0
- package/src/__tests__/cli/install.test.ts +161 -0
- package/src/__tests__/cli/opencode.test.ts +169 -0
- package/src/__tests__/cli/pi.test.ts +113 -0
- package/src/__tests__/cli.test.ts +70 -0
- package/src/__tests__/events/crossProcess.test.ts +82 -0
- package/src/__tests__/events/index.test.ts +32 -0
- package/src/__tests__/extensions.test.ts +81 -0
- package/src/__tests__/migrations/retention.test.ts +118 -0
- package/src/__tests__/queue/index.test.ts +61 -0
- package/src/__tests__/scheduler/index.test.ts +39 -0
- package/src/__tests__/vec/index.test.ts +130 -0
- package/src/__tests__/vec/parity.test.ts +70 -0
- package/src/adapterTypes.ts +23 -0
- package/src/cli/_shared.ts +120 -0
- package/src/cli/bin.ts +97 -0
- package/src/cli/claude.ts +124 -0
- package/src/cli/detect.ts +55 -0
- package/src/cli/hook.ts +25 -0
- package/src/cli/index.ts +22 -0
- package/src/cli/install.ts +185 -0
- package/src/cli/opencode.ts +193 -0
- package/src/cli/pi.ts +74 -0
- package/src/cli/types.ts +78 -0
- package/src/config.ts +163 -0
- package/src/context.ts +172 -0
- package/src/dao/conflicts.ts +26 -0
- package/src/dao/contextItems.ts +70 -0
- package/src/dao/embeddings.ts +78 -0
- package/src/dao/index.ts +9 -0
- package/src/dao/provenanceAudit.ts +108 -0
- package/src/dao/types.ts +142 -0
- package/src/db.ts +780 -0
- package/src/dedup.ts +12 -0
- package/src/embeddings.ts +386 -0
- package/src/events/index.ts +130 -0
- package/src/extensions.ts +140 -0
- package/src/graph.ts +268 -0
- package/src/index.ts +95 -0
- package/src/janitor/archive.ts +66 -0
- package/src/janitor/decay.ts +60 -0
- package/src/janitor/dedup.ts +333 -0
- package/src/janitor/embeddings.ts +209 -0
- package/src/janitor/index.ts +215 -0
- package/src/janitor/promote.ts +188 -0
- package/src/janitor/providers/anthropic.ts +91 -0
- package/src/janitor/providers/cohere.ts +135 -0
- package/src/janitor/providers/gemini.ts +156 -0
- package/src/janitor/providers/ollama.ts +177 -0
- package/src/janitor/providers/openai.ts +121 -0
- package/src/janitor/providers/openrouter.ts +182 -0
- package/src/janitor/providers/types.ts +154 -0
- package/src/janitor/providers/utils.ts +35 -0
- package/src/janitor/supersedes.ts +199 -0
- package/src/lib/honker.ts +54 -0
- package/src/lib/honkerTypes.ts +109 -0
- package/src/lib/jsonlLogger.ts +92 -0
- package/src/lib/writeQueue.ts +28 -0
- package/src/migrations.ts +415 -0
- package/src/paths.ts +22 -0
- package/src/proposals.ts +120 -0
- package/src/providers/disabled.ts +19 -0
- package/src/providers/embeddingProvider.ts +49 -0
- package/src/providers/gemini.ts +37 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/ollama.ts +43 -0
- package/src/providers/openai.ts +35 -0
- package/src/queue/index.ts +53 -0
- package/src/queue/worker.ts +77 -0
- package/src/recall/categorise.ts +139 -0
- package/src/recall/explainer.ts +76 -0
- package/src/scheduler/index.ts +97 -0
- package/src/schema.sql +191 -0
- package/src/secretsScrubber.ts +105 -0
- package/src/shared-db.ts +158 -0
- package/src/vec/index.ts +161 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Path Traversal And Installer Safety
|
|
2
|
+
|
|
3
|
+
## Boundary Model
|
|
4
|
+
|
|
5
|
+
Every write, extract, copy, replace, or delete operation needs an explicit allowed root. Normalize the candidate path, resolve it against the root, and verify the final destination remains inside that root before touching the filesystem.
|
|
6
|
+
|
|
7
|
+
## Traversal And Zip-Slip Risks
|
|
8
|
+
|
|
9
|
+
Untrusted paths may contain `..`, absolute prefixes, mixed separators, symlinks, or archive metadata that escapes the intended directory. Zip-slip attacks use crafted archive entries such as `../../outside.txt` to overwrite files outside the extraction root.
|
|
10
|
+
|
|
11
|
+
## Installer Hardening Principles
|
|
12
|
+
|
|
13
|
+
Operate on a fixed list of managed targets. Avoid wildcard writes. Keep overwrite behavior intentional. Refuse to follow symlinks into unexpected locations. Validate both the source artifact and the final destination before moving files into place.
|
|
14
|
+
|
|
15
|
+
## TypeScript Examples
|
|
16
|
+
|
|
17
|
+
### Safe
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
|
|
22
|
+
export function resolveWithinRoot(root: string, candidate: string): string {
|
|
23
|
+
const resolved = path.resolve(root, candidate);
|
|
24
|
+
|
|
25
|
+
if (!resolved.startsWith(`${root}${path.sep}`) && resolved !== root) {
|
|
26
|
+
throw new Error("Path escapes allowed root");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Unsafe
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
|
|
38
|
+
export function installFile(root: string, candidate: string): string {
|
|
39
|
+
return path.join(root, candidate);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`path.join` alone does not prove the final path stays inside the root.
|
|
44
|
+
|
|
45
|
+
## Python Examples
|
|
46
|
+
|
|
47
|
+
### Safe
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def resolve_within_root(root: Path, candidate: str) -> Path:
|
|
54
|
+
resolved = (root / candidate).resolve()
|
|
55
|
+
if root.resolve() not in [resolved, *resolved.parents]:
|
|
56
|
+
raise ValueError("Path escapes allowed root")
|
|
57
|
+
return resolved
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Unsafe
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def write_target(root: Path, candidate: str) -> Path:
|
|
67
|
+
return root / candidate
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Bash Examples
|
|
71
|
+
|
|
72
|
+
### Safe
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
#!/usr/bin/env bash
|
|
76
|
+
set -euo pipefail
|
|
77
|
+
|
|
78
|
+
root_dir="$1"
|
|
79
|
+
candidate="$2"
|
|
80
|
+
target="$(python3 -c 'from pathlib import Path; import sys; root = Path(sys.argv[1]).resolve(); target = (root / sys.argv[2]).resolve(); print(target if root in [target, *target.parents] else "")' "$root_dir" "$candidate")"
|
|
81
|
+
|
|
82
|
+
[[ -n "$target" ]] || { printf 'Path escapes root\n' >&2; exit 1; }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Unsafe
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
#!/usr/bin/env bash
|
|
89
|
+
set -euo pipefail
|
|
90
|
+
|
|
91
|
+
cp "$2" "$1/$3"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
String concatenation does not enforce destination boundaries.
|
|
95
|
+
|
|
96
|
+
## Archive Extraction Checks
|
|
97
|
+
|
|
98
|
+
Validate every entry name before extraction. Reject absolute paths, parent traversal, device names, and symlink targets that resolve outside the root. Treat archive metadata as attacker-controlled input, not as trustworthy filesystem intent.
|
|
99
|
+
|
|
100
|
+
## Review Checklist
|
|
101
|
+
|
|
102
|
+
- [ ] Every filesystem operation resolves against an explicit allowed root.
|
|
103
|
+
- [ ] Final destinations are checked after normalization and resolution.
|
|
104
|
+
- [ ] Installer flows avoid wildcard writes and unexpected symlink traversal.
|
|
105
|
+
- [ ] Archive extraction defends against zip-slip and absolute-path escapes.
|
|
106
|
+
- [ ] TypeScript, Python, and Bash examples all validate final path boundaries.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: command-path-safety
|
|
3
|
+
description: "USE WHEN hardening command execution, path handling, or installer boundaries."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Command Path Safety
|
|
7
|
+
|
|
8
|
+
Treat commands and paths as structured, validated inputs.
|
|
9
|
+
|
|
10
|
+
## Workflow Routing
|
|
11
|
+
|
|
12
|
+
| Workflow | Trigger | File |
|
|
13
|
+
|---------|---------|------|
|
|
14
|
+
| **HardenCommandExecution** | "command injection", "shell safety", "exec hardening", "safe subprocess" | `Workflows/HardenCommandExecution.md` |
|
|
15
|
+
| **EnforcePathBoundaries** | "path traversal", "zip slip", "installer safety", "path boundaries" | `Workflows/EnforcePathBoundaries.md` |
|
|
16
|
+
|
|
17
|
+
## SkillSearch
|
|
18
|
+
|
|
19
|
+
- Command injection guidance: `SkillSearch('command path safety command injection patterns')` → loads `CommandInjectionPatterns.md`
|
|
20
|
+
- Path and installer guidance: `SkillSearch('command path safety path traversal installer safety')` → loads `PathTraversalAndInstallerSafety.md`
|
|
21
|
+
|
|
22
|
+
## Use This Skill To
|
|
23
|
+
|
|
24
|
+
- Replace shell interpolation with validated structured execution.
|
|
25
|
+
- Enforce path roots for writes, extraction, and installer operations.
|
|
26
|
+
- Review archive and installer behavior for traversal and boundary escapes.
|
|
27
|
+
|
|
28
|
+
## Not This Skill
|
|
29
|
+
|
|
30
|
+
- Not for exploit scanning, malware analysis, or final security verdicts.
|
|
31
|
+
- Hand off to @aegis for deep audits, exploitability assessment, or repo-wide review.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# EnforcePathBoundaries
|
|
2
|
+
|
|
3
|
+
## Use When
|
|
4
|
+
|
|
5
|
+
Use when code writes files, extracts archives, installs assets, or accepts filenames that could escape an intended root.
|
|
6
|
+
|
|
7
|
+
## Procedure
|
|
8
|
+
|
|
9
|
+
1. Define the exact allowed root for the operation and list every candidate source of path input.
|
|
10
|
+
2. Normalize and resolve each candidate path against the allowed root before any filesystem action.
|
|
11
|
+
3. Reject traversal markers, absolute escapes, unexpected symlink targets, and archive entries that resolve outside the root.
|
|
12
|
+
4. Replace wildcard or implicit installer writes with an explicit managed target list.
|
|
13
|
+
5. Re-check the final destination after resolution and before overwrite, extraction, or replacement occurs.
|
|
14
|
+
6. Review TypeScript, Python, and Bash implementations for equivalent boundary gaps, then add adversarial tests.
|
|
15
|
+
|
|
16
|
+
## Done When
|
|
17
|
+
|
|
18
|
+
- [ ] Every destination is proven to remain inside an allowed root.
|
|
19
|
+
- [ ] Traversal, zip-slip, and symlink escapes are rejected.
|
|
20
|
+
- [ ] Installer operations act only on explicit managed targets.
|
|
21
|
+
- [ ] Cross-language path handling follows the same boundary rules.
|
|
22
|
+
|
|
23
|
+
## Escalate To @aegis When
|
|
24
|
+
|
|
25
|
+
- You suspect an active path traversal exploit or malicious archive.
|
|
26
|
+
- The boundary crosses user directories, system paths, or deployment artifacts.
|
|
27
|
+
- You need a wider audit of installer or extraction behavior.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# HardenCommandExecution
|
|
2
|
+
|
|
3
|
+
## Use When
|
|
4
|
+
|
|
5
|
+
Use when code builds shell commands, launches subprocesses, or passes untrusted values into command execution paths.
|
|
6
|
+
|
|
7
|
+
## Procedure
|
|
8
|
+
|
|
9
|
+
1. Identify the executable, every argument source, and every place untrusted data could influence parsing.
|
|
10
|
+
2. Replace shell-built command strings with structured argv invocation wherever the runtime allows it.
|
|
11
|
+
3. Validate attacker-controlled fields for separators, substitutions, globs, newlines, and leading-dash option injection.
|
|
12
|
+
4. Insert `--` before positional untrusted values when the target command supports it.
|
|
13
|
+
5. Remove `eval`, `exec` string templates, `os.system`, and comparable dynamic shell patterns from the flow.
|
|
14
|
+
6. Review TypeScript, Python, and Bash entry points for equivalent weaknesses, then add adversarial tests.
|
|
15
|
+
|
|
16
|
+
## Done When
|
|
17
|
+
|
|
18
|
+
- [ ] Commands use explicit executable plus arguments.
|
|
19
|
+
- [ ] Metacharacter and option-injection risks are addressed.
|
|
20
|
+
- [ ] Dynamic shell construction paths are removed or tightly constrained.
|
|
21
|
+
- [ ] Cross-language command execution patterns are consistently hardened.
|
|
22
|
+
|
|
23
|
+
## Escalate To @aegis When
|
|
24
|
+
|
|
25
|
+
- You need exploitability assessment for an existing injection path.
|
|
26
|
+
- The flow executes downloaded or attacker-supplied programs.
|
|
27
|
+
- Repo-wide command execution review is required.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Cloud Credential Patterns
|
|
2
|
+
|
|
3
|
+
## Recognition Principles
|
|
4
|
+
|
|
5
|
+
Recognize provider credential formats so you can redact, quarantine, or avoid reproducing them. Do not copy live-looking values into code, docs, or tests. Use obviously fake placeholders that communicate format without resembling deployable credentials.
|
|
6
|
+
|
|
7
|
+
## AWS Patterns
|
|
8
|
+
|
|
9
|
+
Common AWS access key identifiers begin with prefixes such as `AKIA` or `ASIA`, followed by additional uppercase alphanumeric characters. Session tokens and secret access keys are separate values and should never be shown in full.
|
|
10
|
+
|
|
11
|
+
Use placeholders like `AKIA_FAKE_EXAMPLE_ONLY` or `ASIA_FAKE_SESSION_ONLY`. Keep them visibly synthetic with separators or words.
|
|
12
|
+
|
|
13
|
+
## GCP Patterns
|
|
14
|
+
|
|
15
|
+
Google API keys often begin with `AIza`. Service account credentials are usually JSON documents containing keys such as `type`, `project_id`, `client_email`, and `private_key`. Never reproduce a realistic private key block or full JSON blob from a live account.
|
|
16
|
+
|
|
17
|
+
Use placeholders like `AIza_FAKE_EXAMPLE_ONLY` and `"private_key": "<REDACTED-FAKE-KEY>"`.
|
|
18
|
+
|
|
19
|
+
## Azure Patterns
|
|
20
|
+
|
|
21
|
+
Azure leaks often appear inside connection strings or signed URLs. Watch for fields like `AccountKey=` in storage connection strings and query parameters such as `sig=` in SAS URLs. Redact the value while preserving enough shape to identify the credential class.
|
|
22
|
+
|
|
23
|
+
Use placeholders like `AccountKey=FAKE_EXAMPLE_ONLY` and `sig=FAKE_SIGNATURE_ONLY`.
|
|
24
|
+
|
|
25
|
+
## TypeScript Examples
|
|
26
|
+
|
|
27
|
+
### Safe
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
export function redactCloudSecret(value: string): string {
|
|
31
|
+
if (value.startsWith("AKIA") || value.startsWith("ASIA")) {
|
|
32
|
+
return "<AWS-KEY-REDACTED>";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (value.startsWith("AIza")) {
|
|
36
|
+
return "<GCP-API-KEY-REDACTED>";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return value.replace(/AccountKey=[^;]+/, "AccountKey=<REDACTED>");
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Unsafe
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
export const exampleConfig = {
|
|
47
|
+
accessKeyId: "AKIAFAKEBUTTOOREAL1234",
|
|
48
|
+
apiKey: "AIzaFakeButLooksReal1234567890",
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Even fake examples must be obviously fake, not merely invalid.
|
|
53
|
+
|
|
54
|
+
## Python Examples
|
|
55
|
+
|
|
56
|
+
### Safe
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
def mask_connection_string(text: str) -> str:
|
|
60
|
+
return text.replace("AccountKey=FAKE_EXAMPLE_ONLY", "AccountKey=<REDACTED>")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Unsafe
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
SERVICE_ACCOUNT = {
|
|
67
|
+
"type": "service_account",
|
|
68
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\npretend\n-----END PRIVATE KEY-----",
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Avoid private-key shaped fixtures, even when they are fake.
|
|
73
|
+
|
|
74
|
+
## Bash Examples
|
|
75
|
+
|
|
76
|
+
### Safe
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
#!/usr/bin/env bash
|
|
80
|
+
set -euo pipefail
|
|
81
|
+
|
|
82
|
+
printf 'Using %s\n' 'AWS_ACCESS_KEY_ID=<REDACTED>'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Unsafe
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
#!/usr/bin/env bash
|
|
89
|
+
set -euo pipefail
|
|
90
|
+
|
|
91
|
+
export AZURE_STORAGE_CONNECTION_STRING='DefaultEndpointsProtocol=https;AccountName=demo;AccountKey=FAKE_EXAMPLE_ONLY'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Avoid shell examples that normalize inline secret storage, even with placeholders.
|
|
95
|
+
|
|
96
|
+
## Redaction Guidance
|
|
97
|
+
|
|
98
|
+
Preserve provider clues while removing the secret value. Examples: keep `AKIA...` as `AKIA<REDACTED>`, keep `sig=` as `sig=<REDACTED>`, and keep service account field names while replacing values. The goal is reviewability without leakage.
|
|
99
|
+
|
|
100
|
+
## Review Checklist
|
|
101
|
+
|
|
102
|
+
- [ ] AWS, GCP, and Azure credential shapes are recognized without reproducing realistic values.
|
|
103
|
+
- [ ] Examples use obviously fake placeholders, not plausible credentials.
|
|
104
|
+
- [ ] TypeScript, Python, and Bash examples all prefer redaction over inline storage.
|
|
105
|
+
- [ ] Service account JSON and signed URLs are described safely.
|
|
106
|
+
- [ ] Documentation preserves provider context while removing secret material.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: secret-safe-handling
|
|
3
|
+
description: "USE WHEN handling credentials, env vars, logs, tests, or docs safely."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Secret Safe Handling
|
|
7
|
+
|
|
8
|
+
Reference secret interfaces without exposing secret values.
|
|
9
|
+
|
|
10
|
+
## Workflow Routing
|
|
11
|
+
|
|
12
|
+
| Workflow | Trigger | File |
|
|
13
|
+
|---------|---------|------|
|
|
14
|
+
| **DesignSecretSafeFlow** | "secret flow", "credential wiring", "env vars", "safe secret access" | `Workflows/DesignSecretSafeFlow.md` |
|
|
15
|
+
| **RemoveSecretExposure** | "secret leak", "redact token", "credential exposure", "cleanup secret" | `Workflows/RemoveSecretExposure.md` |
|
|
16
|
+
|
|
17
|
+
## SkillSearch
|
|
18
|
+
|
|
19
|
+
- Cloud credential identification: `SkillSearch('secret safe handling cloud credential patterns')` → loads `CloudCredentialPatterns.md`
|
|
20
|
+
- Safe handling guidance: `SkillSearch('secret safe handling playbook')` → loads `SecretHandlingPlaybook.md`
|
|
21
|
+
|
|
22
|
+
## Use This Skill To
|
|
23
|
+
|
|
24
|
+
- Recognize cloud credential shapes without copying live values.
|
|
25
|
+
- Design code, docs, tests, and logs that avoid secret disclosure.
|
|
26
|
+
- Remediate accidental exposure using redaction and rotation-oriented workflows.
|
|
27
|
+
|
|
28
|
+
## Not This Skill
|
|
29
|
+
|
|
30
|
+
- Not for secret scanning, incident verdicts, or provider-side breach analysis.
|
|
31
|
+
- Hand off to @aegis for detection runs, deep audits, or formal security judgments.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Secret Handling Playbook
|
|
2
|
+
|
|
3
|
+
## Core Practice
|
|
4
|
+
|
|
5
|
+
Secrets should flow through dedicated interfaces such as environment variables, secret managers, injected files with limited scope, or runtime handles. They should not flow through source code literals, screenshots, snapshots, shell history, or debug logs.
|
|
6
|
+
|
|
7
|
+
## Safe Wiring Patterns
|
|
8
|
+
|
|
9
|
+
Use names, paths, and contracts instead of values. Say `process.env.API_TOKEN`, `os.environ["API_TOKEN"]`, or `/run/secrets/api-token` rather than showing any token content.
|
|
10
|
+
|
|
11
|
+
Prefer the shortest path from secret source to secret consumer. Every extra copy creates another leak surface.
|
|
12
|
+
|
|
13
|
+
## Logging Patterns
|
|
14
|
+
|
|
15
|
+
Log secret state, not secret content. Safe logs include presence, source, age, rotation state, or validation outcome. Unsafe logs include full tokens, connection strings, authorization headers, signed URLs, or PEM blocks.
|
|
16
|
+
|
|
17
|
+
## Test And Documentation Patterns
|
|
18
|
+
|
|
19
|
+
Tests should use synthetic placeholders such as `<TOKEN_FROM_TEST_FIXTURE>` or `FAKE_EXAMPLE_ONLY`. Documentation should show where a value goes and how to inject it, not what a realistic value looks like.
|
|
20
|
+
|
|
21
|
+
Snapshots, screenshots, and copy-paste setup guides often become leak vectors. Review them as carefully as source code.
|
|
22
|
+
|
|
23
|
+
## TypeScript Examples
|
|
24
|
+
|
|
25
|
+
### Safe
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
export function readApiToken(): string {
|
|
29
|
+
const token = process.env.API_TOKEN;
|
|
30
|
+
|
|
31
|
+
if (!token) {
|
|
32
|
+
throw new Error("API_TOKEN is required");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return token;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Unsafe
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
export const API_TOKEN = "FAKE_TOKEN_EXAMPLE_ONLY";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Even fake literals teach the wrong integration pattern.
|
|
46
|
+
|
|
47
|
+
## Python Examples
|
|
48
|
+
|
|
49
|
+
### Safe
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import os
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_db_password() -> str:
|
|
56
|
+
password = os.environ["DB_PASSWORD"]
|
|
57
|
+
return password
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Unsafe
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
def debug_secret(password: str) -> None:
|
|
64
|
+
print(f"db password={password}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Bash Examples
|
|
68
|
+
|
|
69
|
+
### Safe
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
#!/usr/bin/env bash
|
|
73
|
+
set -euo pipefail
|
|
74
|
+
|
|
75
|
+
if [[ -z "${API_TOKEN:-}" ]]; then
|
|
76
|
+
printf 'API_TOKEN is missing\n' >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Unsafe
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
#!/usr/bin/env bash
|
|
85
|
+
set -euo pipefail
|
|
86
|
+
|
|
87
|
+
curl -H "Authorization: Bearer ${API_TOKEN}" "https://example.invalid"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Inline shell expansion can leak into process listings, traces, and logs.
|
|
91
|
+
|
|
92
|
+
## Cleanup Strategy For Exposed Secrets
|
|
93
|
+
|
|
94
|
+
When exposure occurs, first contain the artifact, then redact visible copies, then plan rotation with the system owner. Code cleanup alone is not enough if the secret may already have been observed. Preserve evidence for incident response without repeating the secret.
|
|
95
|
+
|
|
96
|
+
## Review Checklist
|
|
97
|
+
|
|
98
|
+
- [ ] Secrets enter code through approved interfaces instead of literals.
|
|
99
|
+
- [ ] Logs, tests, docs, and screenshots avoid revealing secret values.
|
|
100
|
+
- [ ] TypeScript, Python, and Bash examples model safe integration patterns.
|
|
101
|
+
- [ ] Redaction preserves context without disclosing the value.
|
|
102
|
+
- [ ] Cleanup guidance includes containment and rotation-oriented follow-up.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# DesignSecretSafeFlow
|
|
2
|
+
|
|
3
|
+
## Use When
|
|
4
|
+
|
|
5
|
+
Use when designing code, docs, tests, or automation that must access credentials without exposing them.
|
|
6
|
+
|
|
7
|
+
## Procedure
|
|
8
|
+
|
|
9
|
+
1. Identify the secret producer, the secret consumer, and every place the value could be copied or logged.
|
|
10
|
+
2. Choose a dedicated handoff mechanism such as environment injection, secret manager lookup, or restricted runtime file.
|
|
11
|
+
3. Replace any literal examples with interface-only references, placeholders, or redacted shapes.
|
|
12
|
+
4. Audit logs, error paths, tests, and docs to ensure they reveal state only, not secret content.
|
|
13
|
+
5. Minimize secret lifetime and duplication by passing the value directly to the consumer with the fewest intermediate hops.
|
|
14
|
+
6. Review the design for leak surfaces across TypeScript, Python, and Bash touchpoints, then add adversarial tests.
|
|
15
|
+
|
|
16
|
+
## Done When
|
|
17
|
+
|
|
18
|
+
- [ ] No design artifact embeds a secret literal.
|
|
19
|
+
- [ ] Secret flow uses an approved injection or retrieval path.
|
|
20
|
+
- [ ] Logs, docs, and tests show interface shape without value disclosure.
|
|
21
|
+
- [ ] Leak surfaces are identified and reduced.
|
|
22
|
+
|
|
23
|
+
## Escalate To @aegis When
|
|
24
|
+
|
|
25
|
+
- You suspect a real credential has already been exposed.
|
|
26
|
+
- You need repo-wide secret detection or incident-oriented review.
|
|
27
|
+
- The flow touches multiple systems and requires a formal security judgment.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# RemoveSecretExposure
|
|
2
|
+
|
|
3
|
+
## Use When
|
|
4
|
+
|
|
5
|
+
Use when code, docs, logs, tests, or examples contain a secret or secret-like value that must be contained and cleaned up.
|
|
6
|
+
|
|
7
|
+
## Procedure
|
|
8
|
+
|
|
9
|
+
1. Identify every artifact that contains the exposed value or a derived copy such as logs, snapshots, comments, or transcripts.
|
|
10
|
+
2. Contain further spread by removing the value from active prompts, outputs, and generated artifacts.
|
|
11
|
+
3. Replace the exposed value with a redacted marker that preserves provider or field context.
|
|
12
|
+
4. Update code, docs, tests, and shell snippets so future examples use interface-only references or obviously fake placeholders.
|
|
13
|
+
5. Document whether rotation or owner notification is required without repeating the secret in the note.
|
|
14
|
+
6. Re-review the cleaned artifacts for residual copies and screenshot risk, then add adversarial tests.
|
|
15
|
+
|
|
16
|
+
## Done When
|
|
17
|
+
|
|
18
|
+
- [ ] The exposed value is no longer present in maintained artifacts.
|
|
19
|
+
- [ ] Replacement text is clearly redacted and non-usable.
|
|
20
|
+
- [ ] Follow-up notes avoid reintroducing the secret.
|
|
21
|
+
- [ ] Future examples use safe placeholders or secret interfaces.
|
|
22
|
+
|
|
23
|
+
## Escalate To @aegis When
|
|
24
|
+
|
|
25
|
+
- The exposure may involve a real cloud credential, token, or private key.
|
|
26
|
+
- You need forensic guidance, scan coverage, or incident severity judgment.
|
|
27
|
+
- The leak spans commit history, artifacts, or multiple repositories.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rohirik/openltm-core",
|
|
3
|
+
"version": "2.8.0",
|
|
4
|
+
"description": "Shared LTM storage engine — path-agnostic SQLite core used by Claude Code, OpenCode, and Pi adapters",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./cli": "./src/cli/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"ltm": "src/cli/bin.ts"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/RohiRIK/OpenLtm"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ltm",
|
|
20
|
+
"long-term-memory",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"sqlite",
|
|
23
|
+
"memory"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "^1.3.0",
|
|
31
|
+
"@iarna/toml": "^2.2.5",
|
|
32
|
+
"bun-types": "^1.0.0",
|
|
33
|
+
"sqlite-vec": "0.1.9"
|
|
34
|
+
},
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"@russellthehippo/honker-bun": "^0.2.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/claude.test.ts — Unit tests for the Claude Code installer.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
5
|
+
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
|
|
9
|
+
function makeTmp(): string {
|
|
10
|
+
const dir = join(
|
|
11
|
+
os.tmpdir(),
|
|
12
|
+
`ltm-claude-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
13
|
+
);
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readSettings(tmpDir: string): Record<string, unknown> {
|
|
19
|
+
const p = join(tmpDir, ".claude", "settings.json");
|
|
20
|
+
return JSON.parse(readFileSync(p, "utf8")) as Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("installClaude", () => {
|
|
24
|
+
let tmpDir: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
tmpDir = makeTmp();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("creates settings.json with MCP entry and all 3 hooks", async () => {
|
|
35
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
36
|
+
const result = await installClaude({ homedir: tmpDir });
|
|
37
|
+
expect(result.status).toBe("installed");
|
|
38
|
+
|
|
39
|
+
const s = readSettings(tmpDir);
|
|
40
|
+
const servers = s["mcpServers"] as Record<string, unknown>;
|
|
41
|
+
expect(servers).toBeDefined();
|
|
42
|
+
expect(servers["openltm"]).toBeDefined();
|
|
43
|
+
|
|
44
|
+
const hooks = s["hooks"] as Record<string, unknown[]>;
|
|
45
|
+
expect(hooks).toBeDefined();
|
|
46
|
+
expect(Array.isArray(hooks["SessionStart"])).toBe(true);
|
|
47
|
+
expect(Array.isArray(hooks["PreCompact"])).toBe(true);
|
|
48
|
+
expect(Array.isArray(hooks["PostEditCheck"])).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("second run returns status skipped (idempotent)", async () => {
|
|
52
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
53
|
+
const first = await installClaude({ homedir: tmpDir });
|
|
54
|
+
expect(first.status).toBe("installed");
|
|
55
|
+
|
|
56
|
+
const second = await installClaude({ homedir: tmpDir });
|
|
57
|
+
expect(second.status).toBe("skipped");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("preserves existing mcpServers entries from other tools", async () => {
|
|
61
|
+
// Pre-create a settings.json with another MCP server
|
|
62
|
+
mkdirSync(join(tmpDir, ".claude"), { recursive: true });
|
|
63
|
+
writeFileSync(
|
|
64
|
+
join(tmpDir, ".claude", "settings.json"),
|
|
65
|
+
JSON.stringify({ mcpServers: { existingTool: { command: "npx", args: ["some-tool"] } } }),
|
|
66
|
+
"utf8",
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
70
|
+
await installClaude({ homedir: tmpDir });
|
|
71
|
+
|
|
72
|
+
const s = readSettings(tmpDir);
|
|
73
|
+
const servers = s["mcpServers"] as Record<string, unknown>;
|
|
74
|
+
expect(servers["existingTool"]).toBeDefined();
|
|
75
|
+
expect(servers["openltm"]).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("dryRun=true does not write any files", async () => {
|
|
79
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
80
|
+
const result = await installClaude({ homedir: tmpDir, dryRun: true });
|
|
81
|
+
expect(result.status).toBe("installed");
|
|
82
|
+
|
|
83
|
+
const settingsFile = join(tmpDir, ".claude", "settings.json");
|
|
84
|
+
expect(existsSync(settingsFile)).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("creates settings.json when .claude/ directory does not exist", async () => {
|
|
88
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
89
|
+
// tmpDir has no .claude subdirectory
|
|
90
|
+
const result = await installClaude({ homedir: tmpDir });
|
|
91
|
+
expect(result.status).toBe("installed");
|
|
92
|
+
expect(existsSync(join(tmpDir, ".claude", "settings.json"))).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("includes the correct MCP command and args", async () => {
|
|
96
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
97
|
+
await installClaude({ homedir: tmpDir });
|
|
98
|
+
|
|
99
|
+
const s = readSettings(tmpDir);
|
|
100
|
+
const ltm = (s["mcpServers"] as Record<string, unknown>)["openltm"] as {
|
|
101
|
+
command: string;
|
|
102
|
+
args: string[];
|
|
103
|
+
};
|
|
104
|
+
expect(ltm.command).toBe("bunx");
|
|
105
|
+
expect(ltm.args).toEqual(["@rohirik/openltm-core", "mcp-serve"]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("includes all 3 hook events with correct command", async () => {
|
|
109
|
+
const { installClaude } = await import("../../cli/claude.js");
|
|
110
|
+
await installClaude({ homedir: tmpDir });
|
|
111
|
+
|
|
112
|
+
const s = readSettings(tmpDir);
|
|
113
|
+
const hooks = s["hooks"] as Record<string, Array<{ command: string; args: string[] }>>;
|
|
114
|
+
|
|
115
|
+
for (const event of ["SessionStart", "PreCompact", "PostEditCheck"]) {
|
|
116
|
+
const entries = hooks[event];
|
|
117
|
+
expect(entries.length).toBeGreaterThan(0);
|
|
118
|
+
expect(entries[0]!.command).toBe("bunx");
|
|
119
|
+
expect(entries[0]!.args[0]).toBe("@rohirik/openltm-core");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|