An AI agent browsing your codebase runs into .env files, ~/.aws/credentials, private keys, and CI tokens. Without active protection, those secrets flow straight into the LLM context window, where logs capture them, tool call results expose them, or prompt injection exfiltrates them.
Octocode enforces a hard boundary between untrusted content and the model:
Every byte is scanned and redacted before it reaches the LLM. Secrets are stripped on the way in (inputs) and on the way out (results) β zero configuration required.
You get this by default, for every tool call, over both MCP and CLI.
Three independent redaction stages guard every tool call.
flowchart TD
REQ(["π΅ Tool call"])
subgraph S1["β Input guard"]
I1["Schema & bounds\nstr β€10k Β· arr β€100 Β· depth β€20"]
I2["Injection scan\nshell metacharacters Β· prototype keys Β· circular refs"]
I3["Secret redaction on args"]
I1 --> I2 --> I3
end
subgraph S2["Tool execution"]
T1["GitHub API Β· local FS Β· LSP Β· npm"]
end
subgraph S3["β‘ Content guard (per file / response)"]
C1["Path validation + sensitive-file blocklist"]
C2["Rust scanner 300+ patterns β [REDACTED-TYPE]"]
C3["File-context patterns + Registry extras"]
C1 --> C2 --> C3
end
subgraph S4["β’ Output guard"]
O1["maskSensitiveData on every text item"]
O2["Warn agent when redaction occurred"]
O1 --> O2
end
REQ --> S1
S1 -->|"invalid / secrets in args"| E1(["β Structured error"])
S1 -->|clean| S2
S2 --> S3
S3 -->|"blocked path"| E2(["β Redacted error"])
S3 -->|clean| S4
S4 --> MODEL(["β
Model"])
style E1 fill:#dc3545,color:#fff,stroke:none
style E2 fill:#dc3545,color:#fff,stroke:none
style MODEL fill:#28a745,color:#fff,stroke:none
style REQ fill:#0d6efd,color:#fff,stroke:none
The following table shows what the agent sees after redaction:
| Situation | Output |
|---|---|
| Secret in file content | [REDACTED-AWSACCESSKEYID], [REDACTED-GITHUBPAT], β¦ |
| Secret reaching output boundary | A*I*S*A*4*1*X*β¦ (every-other-char masked) |
| Content too large | [CONTENT-REDACTED-SIZE-LIMIT] |
| Path blocked | Structured error β path never echoed |
The Rust-native RegexSet scanner (with a TypeScript fallback from the same pattern list) covers 300+ patterns across every major cloud, SaaS, and dev-tool credential format.
flowchart LR
IN(["Content string"])
subgraph RUST["Rust RegexSet (primary)"]
R1["300+ provider patterns\nAWS Β· Azure Β· GCP Β· GitHub\nOpenAI Β· Stripe Β· Slack β¦"]
end
subgraph TS["TypeScript layer (secondary)"]
T1["File-context-aware patterns\ne.g. password= only fires\ninside .env / config files"]
T2["Registry extras\ncustom org patterns"]
T1 --> T2
end
MATCH{"Match?"}
REDACT["[REDACTED-TYPENAME]"]
OUT(["Sanitized content\n+ warnings[]"])
IN --> RUST --> MATCH
MATCH -->|"yes"| REDACT --> OUT
MATCH -->|"no"| TS --> OUT
The scanner covers these categories:
| Category | Examples |
|---|---|
| Cloud | AWS (key ID, secret, session token, ARN), Azure (AD, storage, Cosmos), GCP, Alibaba |
| AI providers | OpenAI, Anthropic, Azure OpenAI, Bedrock, AI21, AssemblyAI |
| SaaS / dev tools | GitHub (PAT, fine-grained, OAuth, app), Slack, Stripe, npm, Atlassian, Auth0, Adyen |
| Generic / structural | JWTs, PEM/SSH keys, bearer tokens, passwords in URLs, DB connection strings, high-entropy strings |
File-context-aware patterns avoid false positives in source code: password = only fires when the file path matches .env, config, or secrets; Spring Boot credential patterns only apply to application.yml / application.properties.
Local filesystem access passes through two independent layers, so a bypass of one layer still leaves the other guarding sensitive files.
flowchart TD
REQ2(["File / path request"])
subgraph L1["Layer 1 β Discovery pruning (tree walk)"]
D1["Sensitive dirs pruned from results\n.ssh Β· .aws Β· .kube Β· .docker Β· secrets/ β¦"]
end
subgraph L2["Layer 2 β Read-time access control"]
V1["Resolve + normalize\n WORKSPACE_ROOT β strip ../ β expand ~"]
V2["Prefix-check allowed roots\nHOME (default) + ALLOWED_PATHS + registered roots"]
V3["Sensitive-file blocklist\n*.pem Β· .env Β· id_rsa Β· *.tfstate β¦"]
V4["Symlink re-validation\nrealpath β repeat prefix check"]
V1 --> V2 --> V3 --> V4
end
REQ2 --> L1
L1 -->|"sensitive dir"| HIDE(["β Not visible in results"])
L1 -->|safe| L2
V2 -->|"outside roots"| DENY1(["β Access denied"])
V3 -->|"blocked file"| DENY2(["β Redacted error"])
V4 -->|"symlink escape"| DENY3(["β Blocked"])
V4 -->|safe| READ(["β
Read proceeds"])
style HIDE fill:#dc3545,color:#fff,stroke:none
style DENY1 fill:#dc3545,color:#fff,stroke:none
style DENY2 fill:#dc3545,color:#fff,stroke:none
style DENY3 fill:#dc3545,color:#fff,stroke:none
style READ fill:#28a745,color:#fff,stroke:none
The blocklist matches on file name and directory path, and covers these categories:
| Category | Examples |
|---|---|
| Keys and certs | *.pem, *.key, *.p12, id_rsa, id_ed25519, .ssh/ |
| Credentials | .env, .env.*, .netrc, .npmrc, .git-credentials, *_token, client_secret*.json |
| Cloud and infra | .aws/credentials, .kube/, *.tfstate, *.tfvars, .s3cfg |
| Secret stores | .password-store/, *.kdbx, OS keychains, browser login DBs |
| Shell and history | .bash_history, .zsh_history, .*_history |
| Crypto wallets | wallet.dat, .bitcoin/, .ethereum/ |
| App secrets | wp-config.php, google-services.json, secrets.yml, master.key |
Canonical sources: packages/octocode-engine/src/security/filePatterns.ts and packages/octocode-engine/src/security/pathPatterns.ts.
ALLOWED_PATHS (or local.allowedPaths in .octocoderc) adds roots on top of the HOME default. To turn local tools off entirely, set ENABLE_LOCAL=false.
External commands run through child_process.spawn() with an argument array β not exec β and Octocode rejects shell metacharacters before execution.
| Command | Hardening |
|---|---|
rg |
Explicit flag allowlist; --pre/--pre-glob blocked (arbitrary binary exec). Combined short flags validated char-by-char. |
git |
Only clone + sparse-checkout. file://, git://, http:// URLs blocked (HTTPS only). -c keys allowlisted to safe config (advice.detachedHead, core.autocrlf, http.extraHeader, β¦). |
find |
-exec, -execdir, -ok, -delete, -printf and all exec/write operators blocked. |
grep |
Shared dangerous-pattern scan (`;& |
Resolution order. OCTOCODE_TOKEN β GH_TOKEN β GITHUB_TOKEN β GITHUB_PERSONAL_ACCESS_TOKEN β encrypted on-disk OAuth β gh CLI token. The first non-empty value wins; the four environment variables are protected keys that Octocode never reads from .env or .octocoderc.
On-disk storage. AES-256-GCM encrypted under OCTOCODE_HOME.
Output masking. Output masking covers tokens, so results never echo them.
See Configuration for token setup and credential architecture.
| Check | Limit / action |
|---|---|
| String length | β€ 10,000 chars |
| Array length | β€ 100 items |
| Object nesting | β€ 20 levels |
| Prototype pollution | __proto__, constructor, prototype keys β rejected |
| Circular references | WeakSet ancestor tracking β rejected |
| Numeric ranges | depth / context-lines / limits / offsets clamped at schema layer |
Every tool call runs under a 60-second timeout. MCP clients can cancel through AbortSignal. Both timeout and cancellation return a structured error, so no partial data leaks through. Override the default in either of these ways:
configureSecurity({ defaultTimeoutMs: 30_000 }) // process-wide override
withSecurityValidation(name, handler, { timeoutMs: 10_000 }) // per-tool overrideThe securityRegistry singleton lets you extend security policy before boot β useful for org-specific secrets or multi-tenant deployments.
flowchart LR
subgraph API["securityRegistry"]
A1["addSecretPatterns()\n+ ReDoS safety check"]
A2["addAllowedCommands()"]
A3["addAllowedRoots()"]
A4["addIgnoredPathPatterns()\naddIgnoredFilePatterns()\n+ ReDoS safety check"]
end
A1 & A2 & A3 & A4 --> FREEZE{"freeze()"}
FREEZE -->|"locked β mutations throw"| RUNTIME["Runtime\n(immutable policy)"]
FREEZE -->|"reset() to unfreeze"| API
style RUNTIME fill:#28a745,color:#fff,stroke:none
import { securityRegistry } from '@octocodeai/octocode-engine/security';
securityRegistry.addSecretPatterns([
{ name: 'my-service-token', regex: /mst_[A-Za-z0-9]{32}/ }
]);
securityRegistry.addAllowedCommands(['my-search-tool']);
securityRegistry.addAllowedRoots(['/mnt/shared-workspace']);
securityRegistry.addIgnoredPathPatterns([/\/internal-vault\//]);
securityRegistry.addIgnoredFilePatterns([/\.company-secret$/]);
securityRegistry.freeze(); // lock β throws on any further mutationCustom patterns pass through these guards:
- A ReDoS timing heuristic checks every
regexvalue against 50-, 500-, and 2,000-character inputs, and rejects any pattern that takes 200 ms or longer on one of them. Rejection happens before registration. - Octocode deduplicates repeated names and sources.
securityRegistry.versionincrements on every mutation β use it for cache invalidation.
Octocode protects the agent context boundary β what flows between untrusted content and the model. It does not replace repository secret-scanning, OS-level sandboxing, or network egress controls; run those alongside it.
To report a vulnerability, open a private advisory on the octocode repository rather than a public issue.