A zero-configuration security scanner for React Native and Expo applications that detects vulnerabilities, hardcoded secrets, and security misconfigurations with a single command.
- 18 new rules (105 total): committed signing keys and service accounts, hardcoded Android signing passwords, secrets in
eas.jsonand bundled env vars, insecure.npmrc/.yarnrc.yml,network_security_config.xmlchecks, JWT/OAuth/SQLite/MMKV/cipher misuse and more. See the CHANGELOG for the full list. - Built for CI: SARIF 2.1.0 output for GitHub Code Scanning (
--sarif), baselines for adopting rnsec on an existing codebase (--baseline,--write-baseline), a configurable failure threshold (--fail-on,failOn) and per-ruleseverityOverrides. - Fewer false positives:
__DEV__guards now apply to the guarded code only, public-by-design keys are LOW, findings carry aconfidence, and.gitignoreis respected. - Behaviour changes to be aware of (more findings can appear,
--jsonstdout is pure JSON, Node 20+ required): see the upgrade notes.
npm install -g rnsecnpx rnsec scangit clone https://github.com/adnxy/rnsec.git
cd rnsec
npm install
npm run build
npm linkScan your React Native or Expo project:
rnsec scanView the generated HTML report:
open rnsec-report.htmlThat's it. No configuration needed.
Scan current directory:
rnsec scanHTML Report:
Scan specific project:
rnsec scan --path ./my-appCustom output filenames:
rnsec scan --html security-report.html --output results.jsonCI/CD mode (silent, JSON only):
rnsec scan --silent --output results.jsonConsole JSON output (no files):
rnsec scan --jsonView all security rules:
rnsec rulesScan only changed files:
rnsec scan --changed-files main
rnsec scan --changed-files abc123
# In GitHub Actions (needs fetch-depth: 0 on checkout): origin/${{ github.base_ref }}
rnsec scan --changed-files origin/${{ github.base_ref }}rnsec scan [options]
Options:
-p, --path <path> Project directory to scan (default: current directory)
--html <filename> Custom HTML report filename
--output <filename> Custom JSON report filename
--md <filename> Generate Markdown report for PR comments
--json Output JSON to console only (no HTML/JSON files are written)
--silent Suppress console output
--changed-files <ref> Scan only files changed since git reference (branch, commit, or tag)
--min-confidence <l> Only report findings at or above this confidence: low (default), medium, high
--fail-on <level> Exit 1 when a finding at or above this severity is reported: high (default), medium, low, none
--sarif <filename> Generate a SARIF 2.1.0 report (e.g. rnsec.sarif) for GitHub Code Scanning
--baseline <filename> Hide findings that are listed in this baseline file
--write-baseline <f> Write the current findings to a baseline file and exit 0
--no-gitignore Also scan files that git ignores
-h, --help Display help information
-V, --version Display version numberNotes:
--jsonprints the result to stdout and nothing else: informational lines go to stderr, sornsec scan --json | jqworks. It ignores--htmland--output.--mdand--sariffiles are still written.- Without
--json, rnsec writesrnsec-report.htmlandrnsec-report.jsoninto the current directory (or the names you give with--htmland--output). --changed-filesneeds a git checkout.
0- No finding at or above the--fail-onthreshold (defaulthigh, so only HIGH findings fail the build)1- A finding at or above the threshold was reported, or rnsec hit an error (invalid option value, unreadable baseline,--changed-filesoutside a git repository, ...)
--fail-on none never fails on findings, and --write-baseline always exits 0. The threshold can also be set with failOn in the config file; the command line wins.
The --changed-files option allows you to scan only files that have changed since a specific git reference, making it perfect for CI/CD pipelines and pull request validation.
# Scan files changed since main branch
rnsec scan --changed-files main
# Scan files changed since specific commit
rnsec scan --changed-files abc123def456
# Scan files changed since a tag
rnsec scan --changed-files v1.2.0
# Use in CI/CD with JSON output
rnsec scan --changed-files main --output security.json --silentThe --changed-files option accepts any valid git reference:
- Branch names:
main,develop,feature/new-auth - Commit hashes:
abc123def456,HEAD~1 - Tags:
v1.0.0,release-2024 - Special references:
HEAD,origin/main
In CI checkouts there is usually no local branch for the target, so use origin/<branch> (and fetch full history, e.g. fetch-depth: 0 in actions/checkout).
GitHub Actions:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Run security scan on PR changes
env:
BASE_REF: ${{ github.base_ref }}
run: rnsec scan --changed-files "origin/$BASE_REF" --output security.json --silentGitLab CI:
security-scan:
script:
- rnsec scan --changed-files origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME --output security.json --silent- Faster scans: Only analyzes changed files instead of the entire codebase
- PR-focused: Perfect for pull request validation
- CI/CD optimized: Reduces pipeline execution time
- Incremental security: Focus on new security issues introduced in changes
Generate markdown reports that can be automatically posted as GitHub PR comments, bringing security results directly into your pull requests with advanced features like automatic comment updates, comparison tracking, and security metrics.
# Generate markdown report for PR comment
rnsec scan --md security-report.md --silent
# Combine with changed files for PR-focused scanning
rnsec scan --changed-files main --md pr-security-report.md --silent- Automatic updates: Detects and updates existing comments instead of creating duplicates
- No spam: Keeps PR conversations clean by updating the same comment
- Unique identifier: Uses hidden HTML markers to identify rnsec comments
- New/resolved issues: Automatically shows which issues were introduced or fixed
- Trend indicators: Visual indicators for improving/declining security posture
- Historical data: Stores scan results for comparison between runs
- Security score: 0-100 score based on issue severity and count
- Vulnerability density: Issues per 1000 lines of code
- Trend analysis: Automatic detection of improving/declining/stable trends
- Visual dashboards: Comprehensive metrics in markdown format
- Collapsible sections: Each severity level can be expanded/collapsed
- Code snippets: Shows vulnerable code with syntax highlighting
- Better organization: Reduces clutter for reports with many issues
Copy the example workflow from examples/github-actions/security-scan.yml:
name: π Security Scan
on:
pull_request:
branches: [ main, develop ]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install rnsec
run: npm install -g rnsec
- name: Run security scan
env:
BASE_REF: ${{ github.base_ref || 'main' }}
run: rnsec scan --changed-files "origin/$BASE_REF" --md security-report.md --output rnsec-report.json --silent
continue-on-error: true
- name: Comment PR with security results
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const markdownReport = fs.readFileSync('security-report.md', 'utf8');
const commentIdentifier = '<!-- rnsec-security-report -->';
// Find and update existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existingComment = comments.find(c => c.body?.includes(commentIdentifier));
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: markdownReport
});
} else {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: markdownReport
});
}See examples/gitlab-ci/security-scan.yml for GitLab merge request integration.
See examples/bitbucket-pipelines/bitbucket-pipelines.yml for Bitbucket pull request integration.
See examples/azure-devops/azure-pipelines.yml for Azure Pipelines integration.
The enhanced markdown report includes:
- Summary table with issue counts by severity
- Comparison data showing new/resolved issues since last scan
- Security metrics dashboard with score and trends
- Collapsible findings organized by severity
- Code snippets with syntax highlighting
- Risk assessment with clear action items
- Performance metrics and scan information
--sarif <file> writes a SARIF 2.1.0 report that GitHub shows in the Security tab and as inline annotations on pull requests:
name: rnsec
on:
push:
branches: [ main ]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '20'
- run: npx rnsec scan --sarif rnsec.sarif --silent
- uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: rnsec.sarifThe step uses if: always() so the report is uploaded even when rnsec scan exits 1 because of HIGH findings. Add --fail-on none if the upload should be the only gate. Finding paths in the report are relative to the git repository root, and each result carries a stable fingerprint so GitHub can track alerts across commits.
A baseline lets you adopt rnsec on an existing codebase: record today's findings once, then fail only on new ones.
# 1. Record the current findings (always exits 0) and commit the file
rnsec scan --write-baseline .rnsec-baseline.json
# 2. From now on, only findings that are not in the baseline are reported
rnsec scan --baseline .rnsec-baseline.jsonYou can also set "baseline": ".rnsec-baseline.json" in the config file (relative to the scan root). Each finding is identified by a fingerprint of the rule ID, the file path, the whitespace-normalized source line and an occurrence index, not by its line number, so findings survive code moving around. Changing the flagged line itself makes the finding show up again. The number of hidden findings is printed and stored as baselinedCount in the JSON result. --write-baseline honors --min-confidence.
rnsec supports configuration files to customize the scanning behavior. Create a .rnsec.jsonc or .rnsec.json file in your project root (rnsec looks for .rnsec.json, then .rnsec.jsonc, then .rnsec.config.json). The file is parsed as JSONC, so comments and trailing commas are allowed.
| Key | Description |
|---|---|
ignoredRules |
Rule IDs that never run |
exclude |
Extra glob patterns to skip (see Excluding Files) |
failOn |
Severity that makes rnsec scan exit 1; same values as --fail-on, which takes precedence |
severityOverrides |
Map of rule ID to HIGH, MEDIUM or LOW; applied to the reports, SARIF and the exit code |
baseline |
Baseline file, relative to the scan root (see Baselines) |
respectGitignore |
false also scans git-ignored files, like --no-gitignore |
npmVulnerabilityScanning |
enabled, dataSource (npm-audit or hardcoded) and excludeDevDependencies for the dependency checks |
You can ignore specific rules by adding them to the ignoredRules array:
{
"ignoredRules": [
"ASYNCSTORAGE_SENSITIVE_KEY",
"SENSITIVE_LOGGING"
]
}To find the rule ID for a specific finding, check the ruleId field in the JSON output or HTML report.
Use severityOverrides to raise or lower a rule without disabling it, for example to treat a noisy rule as informational or to make a MEDIUM rule fail the build:
{
"severityOverrides": {
"INSECURE_HTTP_URL": "LOW",
"WEBVIEW_DOM_STORAGE_ENABLED": "HIGH"
}
}Unknown rule IDs print a warning, and an invalid severity is an error.
Add an inline comment on the same line as the finding, or on its own line directly above it. The rule ID is mandatory (there is no blanket ignore), and any text after it is treated as the reason:
// rnsec-ignore: INSECURE_HTTP_URL legacy partner endpoint, tracked in SEC-123
fetch('http://partner.example-corp.com/status');
const key = 'AIza...'; // rnsec-ignore: API_KEY_EXPOSED restricted Firebase web keyUse <!-- rnsec-ignore: RULE_ID --> in AndroidManifest.xml, Info.plist and other XML files, and put multiple IDs in one comment separated by commas.
- Test, mock, fixture, story and vendored code is skipped automatically (
__tests__,__mocks__,*.test.*,*.spec.*,e2e,fixtures,mocks,node_modules,build,dist, ...). Folder names are matched against the path relative to the scan root. - Code that only runs in development builds (
if (__DEV__) { ... },__DEV__ && ...,NODE_ENV !== 'production'branches) is not reported. This is decided per finding, so a__DEV__check elsewhere in a file never hides a real issue. - Files that
.gitignoreexcludes are not scanned (tracked files are always scanned). Use--no-gitignoreor"respectGitignore": falseto scan them too. Outside a git checkout this has no effect. .env,.npmrc/.yarnrc.yml,gradle.propertiesand signing-key or service-account files are only checked when git tracks them. Ignored local copies are never reported, and templates such as.env.exampleare skipped. In a folder that is not a git checkout these files are not checked at all.- Findings carry a
confidence(high,medium,low). Heuristic rules report lower confidence; use--min-confidence mediumto see only the more certain ones.
You can exclude specific files and directories by adding exclude patterns to the exclude array:
{
"exclude": [
"**/scripts/**"
]
}Any pattern supported by fast-glob can be used, for more information see Pattern syntax.
rnsec identifies 105 different security issues across 13 categories:
Common vulnerabilities found:
// Hardcoded API keys and secrets
const API_KEY = 'your_secret_api_key_here'; // Never commit real keys!
// Insecure data storage
await AsyncStorage.setItem('user_token', token);
// Unencrypted HTTP requests
fetch('http://api.example.com/data');
// Weak cryptographic algorithms
const hash = MD5(password);
// Missing security properties
<TextInput value={password} /> // Missing secureTextEntryrnsec implements 105 security rules covering:
| Category | Rules | Description |
|---|---|---|
| Storage | 11 | AsyncStorage, MMKV, Realm and SQLite encryption, SQL injection, SecureStore options, file storage, clipboard, redux-persist |
| Network | 7 | HTTP/WebSocket connections, request timeouts, TLS configuration, certificate pinning, disabled TLS validation, hardcoded IPs |
| Authentication | 8 | JWT handling and signing, OAuth flows and PKCE, biometrics, session timeout, secure text inputs |
| Secrets | 6 | API key detection (provider-specific patterns), bundled env secrets, committed .env files, committed keystores and service-account files, basic-auth credentials |
| Cryptography | 5 | Weak hashes, weak ciphers/ECB mode/static IVs, hardcoded encryption keys, custom crypto, insecure randomness |
| Logging | 2 | Sensitive data exposure in logs and error messages |
| React Native | 13 | Native bridge, deep links, Linking.openURL, eval(), deserialization, navigation params, push notifications, screenshots, integrity checks, SDK risk |
| Debug | 4 | Test credentials, debug endpoints, dev tools and network loggers in production |
| WebView | 11 | JS injection, file access, origin whitelist, mixed content, navigation, postMessage, DOM storage, geolocation, caching, headers |
| Android | 15 | Manifest security, exported components, backup, Keystore, network_security_config.xml, hardcoded signing passwords in Gradle |
| iOS | 12 | App Transport Security, Keychain, pasteboard, file sharing, Info.plist, snapshot protection |
| Expo / EAS | 6 | app.json/app.config.* settings and secrets, eas.json secrets, update URL and code signing, permissions |
| Supply chain | 5 | Vulnerable/deprecated dependencies, suspicious lifecycle scripts, non-registry dependency sources, insecure .npmrc/.yarnrc.yml |
Run rnsec rules to list every rule with its default severity. Severity is the default: some rules report a lower or higher level depending on context, and severityOverrides can change it.
rnsec detects 27+ types of hardcoded API keys and secrets:
- AWS Access Keys, Secret Keys, Session Tokens
- Firebase API Keys
- Google Cloud API Keys, OAuth tokens
- Stripe Keys (Live, Test, Restricted)
- GitHub Personal Access Tokens
- GitLab Personal Access Tokens
- Slack Tokens, Webhooks
- Twilio API Keys, Auth Tokens
- SendGrid API Keys
- Mailgun API Keys
- Mailchimp API Keys
- Heroku API Keys
- DigitalOcean Access Tokens
- Private Keys (RSA, SSH, PGP, PKCS8)
- JWT Tokens
- Bearer Tokens
- Generic API Keys and Secrets
rnsec generates two report formats automatically, and can add more on request:
- Interactive dashboard with filtering capabilities
- Syntax highlighting for code snippets
- Categorized findings by severity
- Quick navigation and search
- Default filename:
rnsec-report.html
- Machine-readable format for automation
- CI/CD pipeline integration
- Programmatic analysis
- Default filename:
rnsec-report.json
--md <file>writes a Markdown report for pull request comments--sarif <file>writes SARIF 2.1.0 for GitHub Code Scanning
Create .github/workflows/security.yml:
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
security:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '20'
- name: Install rnsec
run: npm install -g rnsec
- name: Run security scan
run: rnsec scan --output security.json --silent
- name: Upload reports
uses: actions/upload-artifact@v7
if: always()
with:
name: security-report
path: |
security.json
rnsec-report.htmlname: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
security_scan:
type: build
params:
platform: android
steps:
- name: Security validation only
run: |
echo "π Running security validation..."
echo "Current directory: $(pwd)"
echo "Contents:"
ls -la
# Look for project in current and parent directories
echo "π Searching for project..."
# Check current directory first
if [ -f "package.json" ]; then
PROJECT_DIR="."
else
# Check parent directory
if [ -f "../package.json" ]; then
PROJECT_DIR=".."
else
# Search recursively
PROJECT_DIR=$(find .. -name "package.json" -type f -printf '%h' | head -1)
fi
fi
if [ -z "$PROJECT_DIR" ] || [ ! -f "$PROJECT_DIR/package.json" ]; then
echo "β No package.json found in any location"
echo "π Searching all directories:"
find .. -name "package.json" -type f 2>/dev/null || echo "No package.json found anywhere"
exit 1
fi
echo "β
Found project at: $PROJECT_DIR"
cd "$PROJECT_DIR"
echo "π Project contents:"
ls -la | head -10
# Install dependencies and run security scan
npm install -g rnsec
echo "y" | rnsec scan --output security.json
echo "β
Security validation completed"Add to .gitlab-ci.yml:
security-scan:
stage: test
image: node:20
script:
- npm install -g rnsec
- rnsec scan --output security.json --silent
artifacts:
paths:
- security.json
- rnsec-report.html
when: alwaysstage('Security Scan') {
steps {
sh 'npm install -g rnsec'
sh 'rnsec scan --output security.json --silent'
archiveArtifacts artifacts: 'security.json,rnsec-report.html', allowEmptyArchive: true
}
}Test rnsec with included sample projects:
Vulnerable application (35+ issues):
rnsec scan --path examples/vulnerable-appSecure application (minimal issues):
rnsec scan --path examples/secure-app- Node.js: Version 20 or higher
- Project Type: React Native or Expo application
One command with zero configuration required. Works out of the box with any React Native or Expo project.
105 security rules covering all major vulnerability categories from OWASP Mobile Top 10 to platform-specific issues.
Scans complete projects in seconds using efficient static analysis techniques.
Purpose-built for React Native and Expo with Android and iOS platform-specific checks.
Clear findings with code context, severity levels, and remediation guidance.
JSON, Markdown and SARIF output, baselines and a configurable --fail-on exit code designed for automated security pipelines.
rnsec uses static analysis to examine your codebase without executing it:
- File Walker: Recursively scans project files
- AST Parser: Analyzes JavaScript/TypeScript using Abstract Syntax Trees
- Pattern Matching: Detects secrets using regex patterns
- Rule Engine: Applies security rules to AST nodes
- Platform Scanners: Checks Android and iOS configuration files
- Reporter: Generates HTML and JSON reports
Contributions are welcome! Please see CONTRIBUTING.md for details.
- Report Bugs: Create a bug report
- Request Features: Submit a feature request
- Submit Pull Requests: Open a PR
- Improve Documentation: Help us make the docs better
- Add Security Rules: Contribute new detection rules
See DEVELOPMENT.md for the complete developer guide.
# Clone repository
git clone https://github.com/adnxy/rnsec.git
cd rnsec
# Install dependencies
npm install
# Build project
npm run build
# Run tests
npm test
# Link for local development
npm linkQ: Does rnsec modify my code?
A: No. rnsec is a static analysis tool that only reads your code.
Q: Can I customize which rules run?
A: Yes. Disable a rule with ignoredRules, change its severity with severityOverrides, silence one finding with // rnsec-ignore: RULE_ID, or show only high-confidence findings with --min-confidence. See Configuration. Writing your own custom rules is not supported.
Q: Does it work with TypeScript?
A: Yes. rnsec fully supports both JavaScript and TypeScript.
Q: What about React Native Web?
A: rnsec focuses on mobile security. Web-specific checks are not included.
Q: How do I exclude files or directories?
A: rnsec skips tests, mocks, fixtures and build output by default, and you can add your own globs with the exclude setting in .rnsec.json/.rnsec.jsonc (see Excluding Files). Files that .gitignore excludes are skipped too (use --no-gitignore to include them). Single findings can be silenced with // rnsec-ignore: RULE_ID.
Q: Does it replace manual security audits?
A: No. rnsec is a complementary tool. Professional security audits are still recommended for production applications.
rnsec is a static analysis tool with inherent limitations:
- No Runtime Analysis: Cannot detect issues that only appear during execution
- No Network Testing: Does not test actual API endpoints or network security
- No Binary Analysis: Does not analyze compiled native code
- Pattern-Based Detection: May produce false positives or miss context-dependent issues
- Configuration Required: Some security measures may be configured outside the codebase
- Native and Expo configuration:
network_security_config.xmlis scanned directly, not resolved through the manifest'sandroid:networkSecurityConfig; Expo config rules read only literal values inapp.config.js|ts; CodePush code signing is not checked (Expo Updates signing is) - Git checkout needed for some checks:
.env, credential and registry-config files are only reported when git tracks them
Using rnsec is one part of a comprehensive security strategy:
Do:
- Review all findings manually to understand context
- Use rnsec as part of your development workflow
- Combine with other security tools and practices
- Run scans regularly in CI/CD pipelines
- Address high-severity issues promptly
Don't:
- Rely solely on static analysis for security
- Ignore findings without investigation
- Skip professional security audits for sensitive applications
- Assume passing scans mean complete security
For production applications handling sensitive data, we strongly recommend professional security audits and penetration testing.
- Email: adnanpoviolabs@gmail.com
- Issues: GitHub Issues
- Discussions: GitHub Discussions
If rnsec helps secure your React Native apps, consider supporting its development:
Your sponsorship helps:
- Maintain and improve rnsec
- Add new security rules and features
- Provide faster support and bug fixes
- Keep the project free and open source
If you discover a security vulnerability in rnsec itself, please email adnanpoviolabs@gmail.com directly instead of using public issue trackers.
MIT License - see LICENSE file for details.
Copyright (c) 2024 adnxy
Built for the React Native and Expo community. Special thanks to all contributors and users who help improve mobile security.
Found this useful? Consider giving it a star on GitHub to help others discover it.
{ "ignoredRules": ["ASYNCSTORAGE_SENSITIVE_KEY"], "exclude": ["**/scripts/**"], "failOn": "high", // high (default) | medium | low | none "severityOverrides": { "INSECURE_HTTP_URL": "LOW" }, "baseline": ".rnsec-baseline.json", "respectGitignore": true, // default "npmVulnerabilityScanning": { "enabled": true, "dataSource": "npm-audit", "excludeDevDependencies": false } }