Prompt Engineering Fundamentals

25 min · F3

Prompt Engineering Fundamentals

The difference between Claude producing usable output and producing generic filler is the quality of the prompt. This is not about “magic words” — it is about providing sufficient context, clear constraints, and explicit output expectations. The same principles that make a good incident report (specific, structured, actionable) make a good prompt.


The anatomy of an effective prompt

Every effective prompt has three components. Most bad prompts are missing at least one.

1. Context — What Claude needs to know to produce relevant output. Without context, Claude defaults to the broadest interpretation. “Write a KQL query” produces generic output. “Write a KQL query for Microsoft Sentinel that investigates sign-in anomalies for a user whose account was compromised via AiTM phishing 3 days ago” produces targeted output.

2. Constraints — What the output must include, exclude, or conform to. “Use the SigninLogs table. Include both interactive and non-interactive sign-ins. Time range: 7 days. Exclude known corporate IPs from the watchlist CorporateExternalIPs.” Without constraints, Claude makes assumptions — and its assumptions may not match your environment.

3. Output format — What the response should look like. “Return the query as a code block with inline comments explaining each operator.” Or: “Return a table with columns: Finding, Evidence, Severity, Recommendation.” Specifying format eliminates the most common frustration: Claude producing a great analysis buried in 5 paragraphs of prose when you wanted a bullet list.


System prompts vs user prompts

System prompts (called “Project Instructions” in claude.ai Projects) set persistent behaviour. They are processed before every user message and shape how Claude responds across an entire project. Think of them as standing orders.

A strong security operations system prompt:

You are assisting a senior SOC analyst working in a Microsoft 365 E5
environment with Defender XDR and Microsoft Sentinel. Assume:
- KQL is the query language for all queries
- The Sentinel workspace uses standard Microsoft table names
- The CorporateExternalIPs watchlist exists and contains corporate IPs
- All output should be production-ready and deployable
- UK English for all text output
- No preamble or unnecessary caveats — deliver the output directly
- For KQL queries: include inline comments and entity mapping notes
- For reports: use the Ridgeline IR report template structure

This prompt eliminates 90% of the “Claude gave me a generic response” problem. Every conversation in this project now produces output calibrated to your environment.

User prompts are the individual messages you send within a conversation. They should be specific to the task. The system prompt provides the standing context; the user prompt provides the task.


XML tags for structured prompts

Claude is specifically trained to follow XML tag structures. When you wrap sections of your prompt in XML tags, Claude processes each section with its intended purpose:

<context>
A user account (j.morrison@northgateeng.com) was compromised via AiTM
phishing 3 days ago. Containment was executed: password reset, token
revocation, MFA re-registration. We need to verify that no attacker
activity persists.
</context>

<task>
Write a KQL query that checks for any sign-in activity from
non-corporate IPs in the 72 hours following containment.
</task>

<constraints>
- Use SigninLogs and AADNonInteractiveUserSignInLogs (check both)
- Exclude IPs in the CorporateExternalIPs watchlist
- Include: TimeGenerated, IPAddress, AppDisplayName, AuthenticationRequirement
- Time range: last 72 hours from containment timestamp 2026-03-20T14:00:00Z
</constraints>

<output_format>
KQL code block with inline comments. No prose explanation needed.
</output_format>

Why this works: Each tag creates a processing boundary. Claude does not confuse your context with your constraints. The output format tag tells Claude to produce code, not an essay about code. This structure scales — you can add <example>, <exclusions>, <style> tags as needed.


Try it yourself

Take a KQL query you have written before. Rewrite the request as an XML-tagged prompt using the structure above: <context>, <task>, <constraints>, <output_format>. Send it to Claude. Then send the same request as a single unstructured paragraph. Compare the outputs. The XML version produces more precise, more targeted output because Claude processes each section with its intended purpose rather than parsing your intent from a wall of text.

The XML-tagged prompt produces output that follows your constraints precisely — the right table, the right columns, the right time range, inline comments as requested. The unstructured paragraph produces output that addresses the general intent but may: use a different table than you wanted, include columns you did not ask for, add prose explanation you did not need, or miss a constraint buried in the middle of the paragraph. The difference is most dramatic for complex queries with 3+ constraints.


Few-shot examples — show, do not tell

When you need Claude to follow a specific pattern, providing an example is more effective than describing the pattern in words.

Without few-shot (vague):

Write detection rule documentation in our standard format.

With few-shot (precise):

Write detection rule documentation in this format:

<example>
Rule Name: P1-AiTM-TokenReplayFromNewIP
Severity: High
MITRE: T1550.001
Schedule: 5 min / Lookback: 15 min
Description: Detects successful sign-in from a non-corporate IP
  within 10 minutes of a sign-in from a corporate IP for the same user.
Entity Mapping: Account → UserPrincipalName
KQL: [query]
Tuning Notes: Add VPN exit IPs to CorporateExternalIPs watchlist.
  Expected false positive rate: Medium (VPN failover).
</example>

Now write documentation for this rule: [paste your KQL]

Claude mirrors the format precisely. One example is usually sufficient. Two examples for complex patterns.


Chain-of-thought — making Claude show its work

For complex analysis, asking Claude to reason through the problem produces better results than asking for an immediate answer.

Direct (may produce shallow output):

Is this sign-in suspicious?
[paste sign-in log entry]

Chain-of-thought (produces deeper analysis):

Analyse this sign-in event step by step:
1. What does the AuthenticationRequirement field tell us?
2. Is the IP address corporate or external?
3. What does the user agent indicate about the client?
4. Does the DeviceDetail match a known device?
5. Based on your analysis, is this sign-in legitimate, suspicious,
   or confirmed malicious? Justify your conclusion.

[paste sign-in log entry]

The numbered steps force Claude to consider each evidence point individually rather than jumping to a conclusion. This mirrors how an experienced analyst works — and the step-by-step reasoning catches details that a quick assessment misses.


Output control patterns

Control verbosity:

  • “Be concise. No preamble.” → Claude delivers the output directly
  • “Explain your reasoning in detail.” → Claude provides comprehensive analysis
  • “Answer in 3 sentences or fewer.” → Hard length constraint

Control format:

  • “Return as a markdown table with columns: X, Y, Z”
  • “Return as a numbered list”
  • “Return as a KQL code block with inline comments”
  • “Return as JSON matching this schema: {}”

Control tone:

  • “Write for a CISO audience — executive language, no technical jargon”
  • “Write for a SOC analyst — technical, precise, assume deep M365 knowledge”
  • “Write for an employment tribunal — factual, evidence-based, no conclusions about intent”

Security-specific prompting patterns

Pattern 1: Investigation analysis. Provide the log data, the investigation context, and the specific questions to answer.

<context>Investigating potential BEC. User a.patel's mailbox.</context>
<data>[paste CloudAppEvents output]</data>
<questions>
1. Which emails were accessed from non-corporate IPs?
2. Were any inbox rules created during the suspicious window?
3. Is there evidence of email forwarding to external addresses?
</questions>

Pattern 2: Detection rule generation. Provide the attack technique, the log sources, and the deployment parameters.

Write a Sentinel analytics rule that detects [technique].
Log source: [table name]
Schedule: [frequency] / Lookback: [window]
Severity: [level]
MITRE technique: [ID]
Include: entity mapping, custom details, tuning notes.

Pattern 3: Report section drafting. Provide the facts, the audience, and the structure.

Draft the "Technical Findings" section of an IR report.
<facts>[paste investigation timeline and evidence]</facts>
<audience>CISO and non-technical board members</audience>
<structure>Use this section format: Summary → Timeline → Impact → Recommendations</structure>

Pattern 4: Policy generation. Provide the framework, the scope, and the organisational context.

Draft an Acceptable Use Policy for AI tools (Claude, ChatGPT, Copilot).
<framework>Align to ISO 27001:2022 A.5.10 (Acceptable use of information)</framework>
<scope>All employees. UK-based engineering company. 500 staff.</scope>
<requirements>Must cover: approved tools, prohibited uses, data classification rules,
monitoring disclosure, and consequences for violations.</requirements>

Common prompting mistakes

The 80/20 of prompt engineering

You do not need to master every prompting technique. Three patterns cover 80% of security work: (1) XML-tagged prompts for structured tasks, (2) few-shot examples for format compliance, and (3) chain-of-thought for analytical conclusions. Master these three and you are more effective with Claude than most users who have read every prompting guide on the internet.

Try it yourself

Build a prompt using Pattern 2 (detection rule generation) for a real technique in your environment. Include: the technique description in log terms, the log source, the schedule, severity, MITRE mapping, and request tuning notes. Run it. Is the output deployable? What would you change? This exercise produces a real detection rule draft while practising the most common security-specific prompt pattern.

Claude produces a KQL detection rule with the specified parameters. Common issues to check: table and column names (verify against your schema), watchlist references (does your environment use the same watchlist name?), threshold values (Claude's defaults may be too sensitive or too loose for your environment), and entity mapping (verify the field paths match your data). The output is typically 80-90% deployable — the remaining refinement is your environment knowledge applied to Claude's draft.

Mistake 1: No context. “Write a KQL query for sign-in analysis.” → Claude produces a generic query. Fix: add the specific investigation scenario, user, time range, and what you are looking for.

Mistake 2: Ambiguous output expectations. “Analyse this data.” → Claude produces a 500-word essay. Fix: “Analyse this data. Return a table with columns: Finding, Evidence, Risk Level.”

Mistake 3: Asking Claude to verify itself. “Is this output correct?” → Claude will almost always say yes, even if it is wrong. Fix: verify outputs manually against your environment, documentation, or a second query.

Mistake 4: Overly long prompts without structure. A 2,000-word prompt with no XML tags, no section breaks, and multiple tasks embedded in a single paragraph. Fix: use XML tags, separate tasks into distinct messages, and be explicit about what each section is for.


Knowledge checks

Check your understanding

1. You want Claude to write KQL queries that always use your organisation's watchlist name ("CorporateExternalIPs") and include inline comments. Where do you put this instruction?

In the system prompt (Project Instructions). This instruction applies to every query, not just one — so it belongs in the persistent project context. Set it once and every conversation in the project follows it automatically. Repeating it in every user prompt wastes tokens and is error-prone.
In every user prompt — Claude forgets between messages
In the Memory settings — it carries across all projects

2. Claude produces a KQL query that looks correct but references a table called "UserRiskScores." You ask Claude "Is this table name correct?" and Claude says yes. Is the table name correct?

You cannot trust Claude's self-verification. Claude generates statistically probable output — and when asked to verify its own output, it applies the same statistical process. It does not check against your environment. Verify the table name by checking your Sentinel workspace: Logs → Schema → search for the table. Or run a test query: "UserRiskScores | take 1". If the table does not exist, the query fails. Always verify against your environment, never against Claude's self-assessment.
Yes — Claude confirmed it is correct
Ask Claude a second time for more confidence

3. You need Claude to analyse a sign-in event and determine whether it is suspicious. Which prompting pattern produces the most thorough analysis?

Chain-of-thought. Ask Claude to reason through each evidence field step by step: AuthenticationRequirement, IP, user agent, device detail, then conclusion. This forces Claude to evaluate each indicator individually before forming a conclusion — producing analysis that catches details a direct "is this suspicious?" prompt would miss.
Direct question: "Is this sign-in suspicious?"
Just paste the log entry with no instructions

Key takeaways

Every prompt needs context, constraints, and output format. Missing any one produces generic output.

System prompts set persistent behaviour. Use Projects. Configure the system prompt once. Every conversation benefits.

XML tags create processing boundaries. Use them for structured prompts — they are the most reliable way to separate context from task from constraints.

Few-shot examples beat descriptions. Show Claude the format you want. One example is usually enough.

Chain-of-thought improves complex analysis. Ask Claude to reason step by step for investigation and analysis tasks.

Never trust self-verification. Verify all Claude output against your environment, not against Claude’s opinion of its own output.