Skip to main content

Code Safety

Regex-based OWASP Top 10 code safety scanning for LLM-generated code. Detects insecure patterns in fenced code blocks of streamed output and user input, and in code passed to scan_code, using language-aware rule sets. Pure regex: no models, and no runtime dependency beyond @framers/agentos.

Package: @framers/agentos-ext-code-safety


Overview​

The Code Safety extension provides two modes of operation:

  • Passive protection via a guardrail that scans fenced code blocks in streamed agent output and in user input (see Tool Call Scanning for tool arguments)
  • Active capability via an agent-callable tool (scan_code) for on-demand code safety scanning

It detects:

  • Injection -- eval(), exec(), os.system(), dynamic code execution
  • SQL injection -- string concatenation in SQL queries, format-string injection
  • XSS -- innerHTML, document.write, dangerouslySetInnerHTML
  • Path traversal -- unsanitized ../ in file paths, user-input path construction
  • Hardcoded secrets -- AWS keys, passwords, tokens, private keys in code
  • Weak cryptography -- MD5/SHA1 for passwords, Math.random() for security
  • Insecure deserialization -- pickle.loads(), yaml.load() without SafeLoader
  • SSRF -- unvalidated URL construction from user input
  • Incorrect permissions -- chmod 777, world-writable files

All detection is stateless and synchronous via compiled regex patterns. No ML models, no network calls, no external dependencies.


Installation​

npm install @framers/agentos-ext-code-safety

No additional dependencies required.


Usage​

Direct factory usage​

import { AgentOS } from '@framers/agentos';
import { createCodeSafetyGuardrail } from '@framers/agentos-ext-code-safety';

const codeSafetyPack = createCodeSafetyGuardrail({
severityActions: {
critical: 'block',
high: 'block',
medium: 'flag',
low: 'flag',
},
});

const agentos = await AgentOS.create({
extensionManifest: { packs: [{ factory: () => codeSafetyPack }] },
});

Manifest-based loading​

const agentos = await AgentOS.create({
extensionManifest: {
packs: [
{
package: '@framers/agentos-ext-code-safety',
options: {
disabledRules: ['weak-crypto-hash'], // allow MD5 for non-security use
guardrailScope: 'output',
},
},
],
},
});

Language-Aware Patterns​

Rules have per-language regex variants. The universal '*' key matches in any language. Language-specific keys add patterns that only apply when that language is detected.

When scanning Python code, both '*' and 'python' patterns run. When the language cannot be determined, only universal '*' patterns apply.

Supported Languages​

Python, JavaScript/TypeScript, SQL, Ruby, PHP, Bash, Go, Java.

Language Detection​

  1. Fence tag -- extracted from markdown code fence (e.g., ```python)

  2. Keyword heuristics -- when no fence tag is present, detected via patterns:

    • Python: def name(/from ... import/a top-level import/an indented block after a colon
    • SQL: SELECT/INSERT INTO/CREATE TABLE/UPDATE ... SET/DROP TABLE
    • Ruby: class Foo < Bar/.each do |x|/#{...} interpolation/a line ending in end
    • PHP: <?php/$variable =
    • Bash: shebang/if [/echo "/$(cmd
    • Go: package name/func name(/fmt.Print
    • Java: public class/System.out./import java.
    • JavaScript: function name(/const x =/=>/require(/console.

    The checks run in that order and the first match wins. A fence tag of ts or tsx selects typescript patterns; unfenced TypeScript is detected as javascript.


Code Fence Extraction​

The guardrail extracts code blocks from markdown text using a lightweight fence parser:

Here is some code:

` ` `python
import os
os.system("rm -rf /")
` ` `

This text is not scanned.

Only content inside triple-backtick fences is scanned. Non-code text passes through with zero overhead.


Default Rules (25 rules)​

Injection (5 rules)​

Rule IDSeverityWhat It Detects
code-injection-evalcriticaleval(), exec(), compile(), new Function(), setTimeout("...")
command-injection-systemcriticalos.system(), subprocess.call(shell=True), system()
command-injection-backtickhighBacktick execution in Ruby/Perl/Bash, $(...) command substitution
code-injection-templatemediumTemplate literals inside eval/Function, f-strings in exec
unsafe-reflectionmediumClass.forName(userInput), getattr(obj, userInput)

SQL Injection (3 rules)​

Rule IDSeverityWhat It Detects
sql-injection-concatcriticalString concatenation in SQL: "SELECT * FROM " + var, f-strings, template literals
sql-injection-formathigh.format() or %s in SQL strings
sql-injection-keywordshigh' OR 1=1, UNION SELECT, ; DROP TABLE in string literals

XSS (3 rules)​

Rule IDSeverityWhat It Detects
xss-innerhtmlhigh.innerHTML =, v-html=, [innerHTML]=
xss-document-writehighdocument.write(), document.writeln()
xss-dangerously-setmediumdangerouslySetInnerHTML (flagged for review, not blocked)

Path Traversal (2 rules)​

Rule IDSeverityWhat It Detects
path-traversal-dotdothigh../ repeated 2+ times in string literals, ..\\
path-traversal-user-inputmediumpath.join(userInput, os.path.join(request., open(user

Secrets (4 rules)​

Rule IDSeverityWhat It Detects
hardcoded-aws-keycriticalAKIA[0-9A-Z]{16} pattern
hardcoded-passwordhighpassword = "...", passwd =, secret = "..."
hardcoded-tokenhighBearer eyJ, token = "eyJ...", api_key = "..." with 20+ chars
hardcoded-private-keycriticalPEM BEGIN ... PRIVATE KEY headers: plain, RSA, EC, DSA and OPENSSH

Crypto (2 rules)​

Rule IDSeverityWhat It Detects
weak-crypto-hashmediummd5(), sha1(), hashlib.md5, hashlib.sha1 in password context
insecure-randommediumMath.random() in security context, random.random() for tokens

Deserialization (2 rules)​

Rule IDSeverityWhat It Detects
insecure-picklecriticalpickle.loads(), pickle.load(), cPickle.loads()
insecure-yamlhighyaml.load() without Loader=SafeLoader

SSRF (2 rules)​

Rule IDSeverityWhat It Detects
ssrf-unvalidated-urlhighfetch(userInput), requests.get(user_, urllib.request.urlopen(user
ssrf-redirect-followmediumredirect: 'follow' with dynamic URL

Permissions (2 rules)​

Rule IDSeverityWhat It Detects
world-writablehighchmod 777, os.chmod(path, 0o777)
insecure-tmplowopen('/tmp/...) without tempfile or mkstemp

Tool Call Scanning​

The guardrail's evaluateOutput() scans the code argument of a TOOL_CALL_REQUEST chunk that targets a registered code-executing tool. The AgentOS guardrail dispatcher passes output guardrails only TEXT_DELTA chunks (to guardrails with streaming evaluation, as this one has) and the chunks that carry isFinal: true, so a TOOL_CALL_REQUEST in an AgentOS stream never reaches it and tool arguments are not scanned there. A host that calls evaluateOutput() with such a chunk itself gets the scan, with the mapping below.

Default Tool Mapping​

Tool NameArgument KeyDefault Language
shell_executecommandbash
run_sqlquerysql
write_filecontentauto-detect
create_filecontentauto-detect
edit_filecontentauto-detect

Custom Tool Mapping​

const pack = createCodeSafetyGuardrail({
codeExecutingTools: ['shell_execute', 'run_sql', 'my_custom_tool'],
codeArgumentMapping: {
my_custom_tool: { argKey: 'script', language: 'python' },
},
});

Custom Rules​

Add custom rules using the ICodeSafetyRule interface (src/types.ts of @framers/agentos-ext-code-safety):

interface ICodeSafetyRule {
id: string;
name: string;
description: string;
category: CodeSafetyCategory;
severity: 'critical' | 'high' | 'medium' | 'low';
patterns: Record<string, RegExp[]>; // '*' = universal, 'python' = Python-only, etc.
action?: 'flag' | 'block'; // override severity-to-action mapping
}

Example:

const pack = createCodeSafetyGuardrail({
customRules: [
{
id: 'my-custom-rule',
name: 'Unsafe Database Connection',
description: 'Detects hardcoded database connection strings',
category: 'secrets',
severity: 'high',
patterns: {
'*': [/mongodb:\/\/[^:]+:[^@]+@/i, /postgres:\/\/[^:]+:[^@]+@/i],
},
},
],
});

Configuration​

CodeSafetyPackOptions​

OptionTypeDefaultDescription
customRulesICodeSafetyRule[][]Custom rules to add alongside defaults.
includeDefaultRulesbooleantrueWhether to include the 25 default rules. Set to false for custom-only.
disabledRulesstring[][]Rule IDs to disable from the default set.
severityActionsPartial<Record<severity, action>>{ critical: 'block', high: 'block', medium: 'flag', low: 'flag' }Action mapping by severity.
codeExecutingToolsstring[]['shell_execute', 'run_sql', 'write_file', 'create_file', 'edit_file']Tools whose arguments should be scanned.
codeArgumentMappingRecord<string, { argKey, language }>see defaults aboveMaps tool names to code argument key and language.
guardrailScope'output''output'The only accepted value; evaluateInput() scans fenced code in user input regardless.

Agent Tools​

scan_code​

On-demand code safety scanning. Lets agents proactively check code before writing to files, executing, or presenting to users.

Agent: Let me check this code for security issues before writing it.
-> scan_code({ code: "eval(user_input)", language: "python" })
<- output: {
safe: false,
violations: [
{
ruleId: 'code-injection-eval',
ruleName: 'Unsafe eval() / exec() usage',
category: 'injection',
severity: 'critical',
matchedText: 'eval(',
language: 'python',
action: 'block'
}
],
blocksScanned: 1,
summary: "1 violation(s) in 1 block(s): 1×critical — BLOCKED"
}

Memory Impact​

ComponentHeldWhen Loaded
Rules (25 default regexes)The compiled patterns of the active rulesPack load
Per-stream fence bufferThe streamed text of a stream until its fence closesFirst TEXT_DELTA

The pack loads no model.


Graceful Degradation​

ConditionBehavior
No rules configured (empty set)Guardrail is a no-op, returns null
No code fences in textZero-cost passthrough (no scanning performed)
Unknown languageOnly universal '*' patterns applied
Tool call for non-code-executing toolSkipped (not in codeExecutingTools list)