Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx agentmods add rules/atlassian/forge-skills/ssrfgit clone --depth 1 https://github.com/atlassian/forge-skillsWhat it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5 | $0.00018 | $0.01232 |
| Opus 5 | $0.00009 | $0.00616 |
| Sonnet 5 | $0.00004 | $0.00246 |
| Haiku 4.5 | $0.00002 | $0.00123 |
Grade C, and why
ssrf scanned grade C with 2 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Cloud metadata endpointhighServer-side request forgery
One request to 169.254.169.254 can return temporary IAM credentials.
- Cloud metadata: http://169.254.169.254/ Makes network callslowCapability
Not a fault in itself. Listed so you know the mod talks to something, and to what.
await fetch(webhookUrl); // SSRF to internal services How it starts
The opening of the file, as written. The whole thing — 160 lines — stays where its author put it; the contents beside it link to each section on GitHub.
Context
- Forge apps can make outbound HTTP requests via
requestJira,requestConfluence,fetch, and remote backends. SSRF occurs when user input controls request destinations. - Related CWE: CWE-918 (Server-Side Request Forgery).
- Forge-specific: The
routetemplate literal is designed to prevent SSRF; bypassing it is a key vulnerability.
Scope & Signals
- Safe pattern:
routetemplate literal with validated parameters. - Dangerous patterns:
- String concatenation in API routes.
- User input in
fetch()URLs. - Dynamic URL construction without validation.
- Missing
routeusage with requestJira/requestConfluence.
Vulnerable Patterns
// VULNERABLE - String concatenation instead of route
const issueKey = payload.issueKey; // User controlled
await api.requestJira(`/rest/api/3/issue/${issueKey}/comment`);
// Attacker: issueKey = "../../../admin/sensitive"
// VULNERABLE - Direct URL from user input
const webhookUrl = payload.callbackUrl; // User controlled
await fetch(webhookUrl); // SSRF to internal services
// VULNERABLE - User controls path segment
const endpoint = payload.endpoint; // User controlled
await api.requestJira(route`/rest/api/3/${endpoint}`);
// Less obvious but still risky if endpoint can contain ../
// VULNERABLE - User input in fetch without validation
const imageUrl = payload.avatarUrl;
const response = await fetch(imageUrl); // Could be internal URL
// VULNERABLE - Building URL from parts
const host = payload.host;
const path = payload.path;
await fetch(`https://${host}${path}`); // Host/path injection
Secure Patterns
// SECURE - Using route template literal correctly
import { route } from '@forge/api';
const issueKey = payload.issueKey;
// Route escapes/validates the parameter
await api.requestJira(route`/rest/api/3/issue/${issueKey}/comment`);
// SECURE - Validating before use
const issueKey = payload.issueKey;
if (!/^[A-Z]+-\d+$/.test(issueKey)) {
throw new Error('Invalid issue key format');
}
await api.requestJira(route`/rest/api/3/issue/${issueKey}`);
// SECURE - Allowlist for external URLs
const ALLOWED_HOSTS = ['api.trusted-service.com', 'webhooks.example.com'];
function validateUrl(url) {
const parsed = new URL(url);
if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
throw new Error('URL not in allowlist');
}
if (parsed.protocol !== 'https:') {
throw new Error('HTTPS required');
}
return url;
}
const webhookUrl = validateUrl(payload.callbackUrl);
await fetch(webhookUrl);
// SECURE - Using predefined endpoints only
const ENDPOINTS = {
'issues': '/rest/api/3/issue',
'projects': '/rest/api/3/project'
};
const endpoint = ENDPOINTS[payload.endpointName];
if (!endpoint) {
throw new Error('Unknown endpoint');
}
await api.requestJira(route`${endpoint}`);
Detection Checklist
- Search for
requestJira,requestConfluencewithoutroutetemplate. - Find
fetch()calls with dynamic URLs. - Check for string concatenation in API paths.
- Trace URL components to user input sources.
- Verify external URLs are validated against allowlist.
- Look for URL construction from multiple user inputs.
SSRF Targets in Atlassian Context
# Internal services attackers may target:
- Cloud metadata: http://169.254.169.254/
- Internal APIs: http://localhost:*/
- Other tenants: Different cloudId endpoints
- Admin endpoints: /rest/api/3/configuration
Route Template Protection
// The route`` template literal provides protection:
import { route } from '@forge/api';
// SAFE - route encodes special characters
const userInput = "../admin";
route`/rest/api/3/issue/${userInput}`
// Results in: /rest/api/3/issue/..%2Fadmin (encoded)
// BYPASS ATTEMPT - still safe
const userInput = "TEST-1/../../admin";
route`/rest/api/3/issue/${userInput}`
// Results in properly encoded path
// NOT USING route - VULNERABLE
const path = `/rest/api/3/issue/${userInput}`; // Path traversal works!
PoC / Test Leads
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 2d ago First seen · 160 lines · 18 tokens per session scan C 4caac3b26669
ssrf is a cursor rule published in the GitHub repository atlassian/forge-skills (20 stars, last pushed 2d ago), licensed Apache-2.0. It adds 18 tokens to every session and 1,232 once invoked, about $0.0001 per session on Opus 5. A static security scan graded it C with 2 findings (cloud metadata endpoint, makes network calls). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.
Other cursor rules, from other repositories
project
4DA project rules and conventions.
angular-20
This rule provides comprehensive best practices and coding standards for Angular development, focusing on modern TypeScript, standalone components, signals, and performance optimizations.
dev-standard
Apache Superset development standards and guidelines for Cursor IDE.
coolify-ai-docs
Master reference to all Coolify AI documentation in .ai/ directory.
typescript
Changes to these high-fan-out internals can affect every message, delta, element, or rerun. Keep work in them minimal, and benchmark changes with representative stress-test apps.
python_lib
Tips and guidelines specific to the development of the Streamlit Python library, not applicable to scripts and e2e tests.