JIRA: Redact Sensitive Information from Issue Views
Redacts sensitive content from the responses of JIRA issue-view tools before they reach the caller.
- Direction
- egress
- Rego package
jira.egress.redact_sensitive_info- App
- jira
- Bundle
- atlassian
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:8d1f6a48db9d9572ec3f8ef34c4afd991dd1d9bb1cd9834febdec88f3716e600
jiraatlassianpiisecretsdlpredactionegress
What this policy does
Direction: egress (tool_post_invoke)
Default: allow (transform-only — never denies)
Package: jira.egress.redact_sensitive_info
What it does
Redacts sensitive content from the responses of JIRA issue-view tools before they reach the caller. The goal is durable: prevent PII, credentials, and secrets from leaking out through JIRA issue bodies, comments, worklogs, and linked-issue content.
It is a transform-only egress policy — it never denies a call, it only rewrites
matching content in the response to [REDACTED]. Any tool that is not a JIRA
issue-view tool passes through untouched.
Why egress and not ingress
The risk here is reading secrets that already live inside JIRA issues (pasted into a description, a comment, or a worklog). Those values exist regardless of this gateway, so there is nothing to block at ingress — the leak happens when the content is returned to an MCP client. Masking on the egress (response) path is the only place to catch it.
Scope / tool matching
The policy applies only to JIRA tools that surface issue-body content, matched
by tool-name suffix so it stays portable regardless of the MCP server name
prefix the gateway adds (atlassian-, atlassian-jira-mcp-, etc.):
*-getjiraissue*-searchjiraissuesusingjql*-getcommentsforjiraissue*-getjiraissuecomments*-getworklogsforjiraissue*-getjiraissueworklog*-getjiraissueremoteissuelinks
The tool name is read from both input.resource.name and
input.tool_metadata.name, so the policy matches regardless of which surface
the gateway populates first. Confirm the exact tool names your gateway sends
using the dump-input debug technique before relying on this in production; if
your JIRA MCP server exposes other issue-view tools, add their suffixes to
view_tool_suffixes.
What gets redacted
Redaction works two ways. By pattern in any string value:
- PII — US SSN, credit card numbers, email addresses, US phone numbers
- Cloud / SaaS keys (vendor-prefixed shapes) — AWS access keys (
AKIA…,ASIA…), Google API keys (AIza…) and OAuth tokens (ya29.…), GitHub tokens (ghp_,gho_,ghu_,ghs_,ghr_), GitLab PATs (glpat-…), Slack tokens (xox[abprs]-…), Stripe keys (sk_live_,sk_test_,pk_live_,pk_test_) - OAuth / bearer —
Authorization: Bearer …headers and JWTs - Generic
key: value/key=valuesecret assignments (api_key, password, secret, token, client_secret, credentials, …) - Database connection strings with embedded credentials (postgres, mysql,
mongodb, redis, amqp, mssql/sqlserver URIs; JDBC strings; ADO.NET-style
Server=…;User Id=…;Password=…) - PEM private-key blocks (
-----BEGIN … PRIVATE KEY----- … -----END …-----)
And by field name — any response field whose key is one of password,
passwd, pwd, secret, api_key, apikey, token, access_token,
refresh_token, id_token, client_secret, private_key,
connection_string, or credentials.
Matches are replaced with [REDACTED].
Examples
Redacted (JIRA issue view)
{
"input": {
"action": "tool_post_invoke",
"resource": { "name": "atlassian-jira-mcp-getjiraissue", "type": "tool" },
"tool_metadata": { "name": "atlassian-jira-mcp-getjiraissue" }
}
}
allow = true, with a transform that supplies the redaction patterns,
field names, and replacement = "[REDACTED]" for the gateway to apply to the
response body, plus reason = "Sensitive content redacted from Jira response"
so the redaction is explained in the dashboard.
Passed through (any non-issue-view tool)
{
"input": {
"action": "tool_post_invoke",
"resource": { "name": "atlassian-jira-mcp-getjiraproject", "type": "tool" },
"tool_metadata": { "name": "atlassian-jira-mcp-getjiraproject" }
}
}
allow = true, no transform — the response is returned unchanged.
Composition
This policy is single-purpose and transform-only, so it composes cleanly with access-control policies on the same egress pipeline. Useful companions:
- An ingress policy that blocks writing secrets into JIRA in the first place (so new issues don't accumulate credentials).
- Equivalent egress redaction policies for other Atlassian apps (Confluence,
Bitbucket). See the
bundles/atlassianbundle for the curated Atlassian set.
Known limitations
- Regex over plain text. Detection is pattern-based, so novel or non-standard token formats, short-lived rotating tokens, and custom-shaped secrets may not be caught. Treat this as a high-signal layer, not a complete DLP solution.
- Scope is issue-view tools only. Other JIRA tools that might surface
issue content (e.g. bulk export or attachment-download endpoints) are not
inspected unless you add their suffixes to
view_tool_suffixes. - No identity-based exemptions. All callers get the same redaction. Add an
input.subject.claims-gated branch if a break-glass role needs raw values.
Policy source (Rego)
package jira.egress.redact_sensitive_info
# Transform-only policy — never denies, only redacts sensitive content.
# Scoped to JIRA tools that return issue-body content. Other tools (including
# non-JIRA egress) pass through this policy untouched.
default allow := true
# -----------------------------------------------------------------------------
# Scope: JIRA tools that surface issue content (summary, description, comments,
# worklog, custom fields, linked issues). Suffix matching makes this work
# regardless of the MCP server name (atlassian-, atlassian-jira-mcp-, etc.).
# -----------------------------------------------------------------------------
view_tool_suffixes := {
"-getjiraissue",
"-searchjiraissuesusingjql",
"-getcommentsforjiraissue",
"-getjiraissuecomments",
"-getworklogsforjiraissue",
"-getjiraissueworklog",
"-getjiraissueremoteissuelinks",
}
is_jira_issue_view if {
some suffix in view_tool_suffixes
name := lower(input.resource.name)
endswith(name, suffix)
}
is_jira_issue_view if {
# Egress hooks also expose the tool name under tool_metadata.name — check
# both so we match regardless of which surface the gateway populates first.
some suffix in view_tool_suffixes
name := lower(object.get(input.tool_metadata, "name", ""))
endswith(name, suffix)
}
# -----------------------------------------------------------------------------
# Redaction transform
# -----------------------------------------------------------------------------
transform := {
"redact_patterns": [
# ---- PII ----
`\d{3}-\d{2}-\d{4}`, # US SSN
`\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}`, # Credit card
`[\w.-]+@[\w.-]+\.[\w.-]+`, # Email
`\+?1?[- .]?\(?\d{3}\)?[- .]?\d{3}[- .]?\d{4}`, # US phone
# ---- Cloud / SaaS API keys (vendor-prefixed shapes) ----
`AKIA[0-9A-Z]{16}`, # AWS access key ID
`ASIA[0-9A-Z]{16}`, # AWS temporary (STS) access key
`AIza[0-9A-Za-z_-]{35}`, # Google API key
`ya29\.[0-9A-Za-z_-]+`, # Google OAuth access token
`ghp_[A-Za-z0-9]{36}`, # GitHub personal access token
`gho_[A-Za-z0-9]{36}`, # GitHub OAuth token
`ghu_[A-Za-z0-9]{36}`, # GitHub user-to-server token
`ghs_[A-Za-z0-9]{36}`, # GitHub server-to-server token
`ghr_[A-Za-z0-9]{36}`, # GitHub refresh token
`glpat-[A-Za-z0-9_-]{20}`, # GitLab personal access token
`xox[abprs]-[A-Za-z0-9-]+`, # Slack tokens (bot/app/user/refresh/etc.)
`sk_live_[A-Za-z0-9]{24,}`, # Stripe live secret key
`sk_test_[A-Za-z0-9]{24,}`, # Stripe test secret key
`pk_live_[A-Za-z0-9]{24,}`, # Stripe live publishable key
`pk_test_[A-Za-z0-9]{24,}`, # Stripe test publishable key
# ---- OAuth / bearer ----
`(?i)bearer\s+[A-Za-z0-9._~+/-]+=*`, # Authorization: Bearer <token>
`eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+`, # JWT (header.payload.signature, base64url)
# ---- Generic key/value secret patterns ----
`(?i)(?:api[_-]?key|apikey|secret[_-]?key)\s*[:=]\s*\S+`, # api_key=..., api-key: ...
`(?i)(?:password|passwd|pwd|secret|token|credentials|client[_-]?secret)\s*[:=]\s*\S+`,
# ---- Database connection strings ----
# URI form with embedded user:password (postgres, mysql, mongo, redis, amqp, mssql)
`(?i)(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis(?:s)?|amqps?|mssql|sqlserver)://[^:\s]+:[^@\s]+@[^/\s]+(?:/\S*)?`,
`(?i)jdbc:[a-z0-9]+:[^\s]+`, # JDBC strings
`(?i)(?:Server|Data Source)\s*=\s*[^;]+;\s*(?:User Id|UID)\s*=\s*[^;]+;\s*(?:Password|PWD)\s*=\s*[^;]+`,
# ---- PEM private keys (multi-line, lazy match between BEGIN/END markers) ----
`-----BEGIN [A-Z ]+PRIVATE KEY-----.+?-----END [A-Z ]+PRIVATE KEY-----`,
],
"redact_fields": [
"password",
"passwd",
"pwd",
"secret",
"api_key",
"apikey",
"token",
"access_token",
"refresh_token",
"id_token",
"client_secret",
"private_key",
"connection_string",
"credentials",
],
"replacement": "[REDACTED]",
} if {
is_jira_issue_view
}
# Surfaced on the decision event whenever the redaction is in scope, so the
# dashboard can explain the rewrite.
reason := "Sensitive content redacted from Jira response" if {
is_jira_issue_view
} Canonical source: policy.md on GitHub · raw · raw on this site (.md)
Used in these guides
Related policies
JIRA: Deny Sensitive Project Search and View
Keeps issues that belong to a configurable set of "sensitive" JIRA projects out of read access through the JIRA MCP server.
JIRA: Protect Sensitive Projects from Writes
Blocks write operations against issues that belong to a configurable set of "sensitive" JIRA projects.