@highflame/policy 2.2.41 → 2.2.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/_schemas/agent_ops/context.json +0 -42
  2. package/_schemas/agent_ops/schema.cedarschema +44 -6
  3. package/_schemas/ai_gateway/context.json +300 -0
  4. package/_schemas/ai_gateway/schema.cedarschema +107 -3
  5. package/_schemas/guardrails/context.json +0 -36
  6. package/_schemas/guardrails/detectors.json +0 -1
  7. package/_schemas/guardrails/schema.cedarschema +43 -5
  8. package/_schemas/guardrails/templates/dual_attribution.cedar +110 -0
  9. package/_schemas/guardrails/templates/multi_turn_trajectory.cedar +116 -0
  10. package/_schemas/guardrails/templates/profiles/output_protection/credentials.cedar +35 -0
  11. package/_schemas/guardrails/templates/profiles/output_protection/data_leakage.cedar +63 -0
  12. package/_schemas/guardrails/templates/profiles/output_protection/harmful_content.cedar +58 -0
  13. package/_schemas/guardrails/templates/profiles/output_protection/injection_carried_back.cedar +39 -0
  14. package/_schemas/guardrails/templates/session_risk_accumulation.cedar +103 -0
  15. package/_schemas/guardrails/templates/templates.json +157 -11
  16. package/_schemas/overwatch/context.json +65 -0
  17. package/_schemas/overwatch/detectors.json +47 -0
  18. package/_schemas/overwatch/schema.cedarschema +90 -0
  19. package/dist/agent_ops-context.gen.d.ts +1 -2
  20. package/dist/agent_ops-context.gen.js +0 -2
  21. package/dist/ai_gateway-entities.gen.js +5 -1
  22. package/dist/guardrails-context.gen.d.ts +1 -2
  23. package/dist/guardrails-context.gen.js +0 -2
  24. package/dist/guardrails-defaults.gen.js +751 -11
  25. package/dist/overwatch-context.gen.d.ts +3 -1
  26. package/dist/overwatch-context.gen.js +4 -0
  27. package/dist/overwatch-detectors.gen.js +16 -0
  28. package/dist/parser.d.ts +2 -1
  29. package/dist/parser.js +124 -37
  30. package/dist/service-schemas.gen.d.ts +4 -4
  31. package/dist/service-schemas.gen.js +354 -32
  32. package/package.json +1 -1
@@ -3471,6 +3471,537 @@ when {
3471
3471
  !(["treasury@example.com", "payroll@example.com"].contains(context.action_params.recipient))
3472
3472
  };
3473
3473
  `;
3474
+ const GUARDRAILS_PRIVACY_OUTPUT_PROTECTION_CEDAR = `// =============================================================================
3475
+ // Output Protection — Data Leakage
3476
+ // =============================================================================
3477
+ // Guards what the MODEL SAYS BACK, not what the user sends in.
3478
+ //
3479
+ // Every other guardrails template binds process_response alongside
3480
+ // process_prompt and the tool/file actions, which is right for a threat that
3481
+ // is the same in both directions. Data leakage is not that: the risk is
3482
+ // specific to egress, and a tenant who wants to police only their outputs had
3483
+ // no template to enable. That gap is why one deployed tenant hand-authored a
3484
+ // rule named "Block PII in outputs" and bound it to process_prompt — the only
3485
+ // action that covered responses before the direction split (ADR 0031).
3486
+ //
3487
+ // These rules bind process_response ALONE. That is the point: ADR 0031 created
3488
+ // the distinct trigger precisely so a policy could be scoped to one direction
3489
+ // from its head, and this profile is what that is for. Enabling it cannot
3490
+ // change how prompts are treated.
3491
+ //
3492
+ // Context keys consumed:
3493
+ // - pii_detected: Bool
3494
+ // - pii_count: Long
3495
+ //
3496
+ // Compliance:
3497
+ // - OWASP LLM02 (Sensitive Information Disclosure), OWASP LLM06
3498
+ //
3499
+ // Category: privacy
3500
+ // Namespace: Guardrails
3501
+ // =============================================================================
3502
+
3503
+ @id("privacy.output-block-pii")
3504
+ @name("Block PII in model responses")
3505
+ @description("Blocks process_response when the model's own output contains PII. Scoped to the response direction only — prompts are unaffected.")
3506
+ @severity("high")
3507
+ @tags("category:privacy,threat:data-leak,surface:process-response,detection:rule,owasp:llm02")
3508
+ @reject_message("Response blocked: the model's output contained personal data.")
3509
+ forbid (
3510
+ principal,
3511
+ action == Guardrails::Action::"process_response",
3512
+ resource
3513
+ )
3514
+ when {
3515
+ context has pii_detected && context.pii_detected == true
3516
+ };
3517
+
3518
+ // Bulk disclosure is a separate finding from a single incidental match: a
3519
+ // response carrying many distinct PII items is an exfiltration shape rather
3520
+ // than a mention, and is worth its own severity and message even though the
3521
+ // rule above already blocks it. Kept as a distinct rule so a tenant running
3522
+ // the first in monitor mode can still enforce on bulk.
3523
+ @id("privacy.output-block-bulk-pii")
3524
+ @name("Block bulk PII disclosure in model responses")
3525
+ @description("Blocks process_response when the output carries five or more PII items — a disclosure shape rather than an incidental mention.")
3526
+ @severity("critical")
3527
+ @tags("category:privacy,threat:data-leak,surface:process-response,detection:rule,owasp:llm06")
3528
+ @reject_message("Response blocked: the model's output contained bulk personal data.")
3529
+ forbid (
3530
+ principal,
3531
+ action == Guardrails::Action::"process_response",
3532
+ resource
3533
+ )
3534
+ when {
3535
+ context has pii_count && context.pii_count >= 5
3536
+ };
3537
+ `;
3538
+ const GUARDRAILS_TRUST_SAFETY_OUTPUT_PROTECTION_CEDAR = `// =============================================================================
3539
+ // Output Protection — Harmful Content
3540
+ // =============================================================================
3541
+ // The companion to data_leakage.cedar: what the model says back, judged for
3542
+ // safety rather than for disclosure.
3543
+ //
3544
+ // Same scoping rule and same reason — process_response ALONE (ADR 0031). A
3545
+ // tenant enabling this is saying "hold my model's answers to a standard",
3546
+ // which is a different decision from "police what users send me", and the two
3547
+ // should be separately enableable.
3548
+ //
3549
+ // Thresholds match the org-wide toxicity defaults rather than tightening them.
3550
+ // An output profile that silently enforced stricter limits than the tenant's
3551
+ // existing prompt rules would surprise anyone comparing the two.
3552
+ //
3553
+ // Context keys consumed:
3554
+ // - violence_score: Long (0-100)
3555
+ // - hate_speech_score: Long (0-100)
3556
+ // - sexual_score: Long (0-100)
3557
+ // - injection_score: Long (0-100)
3558
+ //
3559
+ // Compliance:
3560
+ // - OWASP LLM05 (Improper Output Handling), OWASP LLM01
3561
+ //
3562
+ // Category: trust-safety
3563
+ // Namespace: Guardrails
3564
+ // =============================================================================
3565
+
3566
+ @id("trust-safety.output-block-toxic")
3567
+ @name("Block toxic model responses")
3568
+ @description("Blocks process_response when the model's own output scores high for violence or hate speech.")
3569
+ @severity("high")
3570
+ @tags("category:trust-safety,threat:harmful,surface:process-response,detection:ml,owasp:llm05")
3571
+ @reject_message("Response blocked: the model's output was flagged as harmful.")
3572
+ forbid (
3573
+ principal,
3574
+ action == Guardrails::Action::"process_response",
3575
+ resource
3576
+ )
3577
+ when {
3578
+ context has violence_score && context.violence_score >= 70 ||
3579
+ context has hate_speech_score && context.hate_speech_score >= 70
3580
+ };
3581
+
3582
+ @id("trust-safety.output-block-sexual")
3583
+ @name("Block sexual content in model responses")
3584
+ @description("Blocks process_response when the model's own output scores high for sexual content.")
3585
+ @severity("critical")
3586
+ @tags("category:trust-safety,threat:sexual,surface:process-response,detection:ml,owasp:llm05")
3587
+ @reject_message("Response blocked: the model's output was flagged as explicit.")
3588
+ forbid (
3589
+ principal,
3590
+ action == Guardrails::Action::"process_response",
3591
+ resource
3592
+ )
3593
+ when {
3594
+ context has sexual_score && context.sexual_score >= 91
3595
+ };
3596
+ `;
3597
+ const GUARDRAILS_SECURITY_OUTPUT_BLOCK_INJECTION_CARRIED_BACK_CEDAR = `// =============================================================================
3598
+ // Output Protection — Injection Carried Back
3599
+ // =============================================================================
3600
+ // Split from harmful_content.cedar because its category is \`security\`, not
3601
+ // \`trust-safety\`, and a template wrapper may only carry rules of its own
3602
+ // category (HFP-LINT-TMPL-006). The split is the honest outcome: this is a
3603
+ // different threat with a different owner, not a toxicity variant.
3604
+ //
3605
+ // Same scoping rule as the rest of this profile — process_response ALONE.
3606
+ //
3607
+ // Context keys consumed:
3608
+ // - injection_score: Long (0-100)
3609
+ //
3610
+ // Compliance:
3611
+ // - OWASP LLM05 (Improper Output Handling)
3612
+ //
3613
+ // Category: security
3614
+ // Namespace: Guardrails
3615
+ // =============================================================================
3616
+
3617
+ // Injection scored on an OUTPUT is a different finding from injection scored on
3618
+ // a prompt. On the way in it is a user attacking the model; on the way out it
3619
+ // is content the model is handing to the caller — a downstream agent, a
3620
+ // renderer, or another tool — which is OWASP LLM05's improper output handling.
3621
+ // Worth its own rule so the two can be tuned and reasoned about separately.
3622
+ @id("security.output-block-injection-carried-back")
3623
+ @name("Block injection payloads carried back in model responses")
3624
+ @description("Blocks process_response when the output itself scores as an injection payload — content the caller may execute or forward.")
3625
+ @severity("high")
3626
+ @tags("category:security,threat:injection,surface:process-response,detection:ml,owasp:llm05")
3627
+ @reject_message("Response blocked: the model's output contained an injection payload.")
3628
+ forbid (
3629
+ principal,
3630
+ action == Guardrails::Action::"process_response",
3631
+ resource
3632
+ )
3633
+ when {
3634
+ context has injection_score && context.injection_score >= 80
3635
+ };
3636
+ `;
3637
+ const GUARDRAILS_DATA_PROTECTION_OUTPUT_BLOCK_SECRETS_CEDAR = `// =============================================================================
3638
+ // Output Protection — Credentials
3639
+ // =============================================================================
3640
+ // Split from data_leakage.cedar: secrets are category \`data-protection\`, PII is
3641
+ // \`privacy\`, and a template wrapper may only carry rules of its own category
3642
+ // (HFP-LINT-TMPL-006). Relabelling one to fit the other would put a wrong
3643
+ // category on the wire, where it drives signals[] and the severity rollup.
3644
+ //
3645
+ // Same scoping rule as the rest of this profile — process_response ALONE, so a
3646
+ // tenant can police what the model says without touching what users send.
3647
+ //
3648
+ // Context keys consumed:
3649
+ // - secrets_detected: Bool
3650
+ //
3651
+ // Compliance:
3652
+ // - OWASP LLM02 (Sensitive Information Disclosure)
3653
+ //
3654
+ // Category: data-protection
3655
+ // Namespace: Guardrails
3656
+ // =============================================================================
3657
+
3658
+ @id("data-protection.output-block-secrets")
3659
+ @name("Block secrets in model responses")
3660
+ @description("Blocks process_response when the model's own output contains credentials, API keys or tokens — the canonical way a leaked secret reaches a caller.")
3661
+ @severity("critical")
3662
+ @tags("category:data-protection,threat:secrets,surface:process-response,detection:rule,owasp:llm02")
3663
+ @reject_message("Response blocked: the model's output contained credentials.")
3664
+ forbid (
3665
+ principal,
3666
+ action == Guardrails::Action::"process_response",
3667
+ resource
3668
+ )
3669
+ when {
3670
+ context has secrets_detected && context.secrets_detected == true
3671
+ };
3672
+ `;
3673
+ const GUARDRAILS_SECURITY_MULTI_TURN_TRAJECTORY_CEDAR = `// =============================================================================
3674
+ // Multi-Turn Trajectory Escalation
3675
+ // =============================================================================
3676
+ // Blocks a conversation whose TRAJECTORY is an attack, even when no single
3677
+ // message is. A crescendo attack never sends a message alarming enough to
3678
+ // block on its own; it walks the model there in small, plausible steps and
3679
+ // asks for the payoff in a sentence that would pass any single-message filter.
3680
+ //
3681
+ // Shield scores every turn twice and exposes both scores separately:
3682
+ // - injection_pulse_score / jailbreak_pulse_score — the single-turn
3683
+ // classifier. Sees only the current message.
3684
+ // - injection_deep_context_score / jailbreak_deep_context_score — the
3685
+ // multi-turn model, which carries hidden state across turns keyed on
3686
+ // session_id. Sees the whole conversation.
3687
+ //
3688
+ // Because they are separate keys, a rule can fire on the GAP between them:
3689
+ // high trajectory score, low message score. In plain language, "the history
3690
+ // is an attack and this message is not" — a condition a filter that only ever
3691
+ // has one number cannot express. Section 1 is that rule. Section 2 is a safety
3692
+ // net for a trajectory blatant enough to stand on its own.
3693
+ //
3694
+ // Requires a stable session_id on every request in the conversation. With no
3695
+ // session_id there is no threaded state, multi_turn_detection is false, and
3696
+ // none of these rules can fire.
3697
+ //
3698
+ // Detection layers:
3699
+ // - injection (single-turn classifier, always available)
3700
+ // - deepcontext (multi-turn model, always available)
3701
+ //
3702
+ // Context keys consumed:
3703
+ // - multi_turn_detection: Bool — true only when threaded state was used
3704
+ // - injection_deep_context_score, injection_pulse_score: Long (0-100)
3705
+ // - jailbreak_deep_context_score, jailbreak_pulse_score: Long (0-100)
3706
+ //
3707
+ // Compliance:
3708
+ // - OWASP LLM01, OWASP LLM02, MITRE ATLAS AML.T0051, AML.T0054
3709
+ //
3710
+ // Category: security
3711
+ // Namespace: Guardrails
3712
+ // =============================================================================
3713
+
3714
+ // ---------------------------------------------------------------------------
3715
+ // Section 1: Trajectory/turn divergence
3716
+ // The conversation scores as an attack; this message does not.
3717
+ // ---------------------------------------------------------------------------
3718
+
3719
+ @id("security.block-trajectory-injection-divergence")
3720
+ @name("Block multi-turn injection the current turn hides")
3721
+ @description("Blocks process_prompt and process_response when threaded state is in use, injection_deep_context_score >= 60, and injection_pulse_score <= 39.")
3722
+ @severity("critical")
3723
+ @tags("category:security,threat:injection,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm01,mitre:atlas-t0051")
3724
+ @reject_message("Prompt blocked: this conversation is a progressive prompt-injection attempt. No single message crossed the line; the sequence did.")
3725
+ forbid (
3726
+ principal,
3727
+ action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
3728
+ resource
3729
+ )
3730
+ when {
3731
+ context has multi_turn_detection && context.multi_turn_detection == true &&
3732
+ context has injection_deep_context_score && context.injection_deep_context_score >= 60 &&
3733
+ context has injection_pulse_score && context.injection_pulse_score <= 39
3734
+ };
3735
+
3736
+ @id("security.block-trajectory-jailbreak-divergence")
3737
+ @name("Block multi-turn jailbreak the current turn hides")
3738
+ @description("Blocks process_prompt and process_response when threaded state is in use, jailbreak_deep_context_score >= 60, and jailbreak_pulse_score <= 39.")
3739
+ @severity("critical")
3740
+ @tags("category:security,threat:jailbreak,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm02,mitre:atlas-t0054")
3741
+ @reject_message("Prompt blocked: this conversation is a progressive jailbreak. No single message crossed the line; the sequence did.")
3742
+ forbid (
3743
+ principal,
3744
+ action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
3745
+ resource
3746
+ )
3747
+ when {
3748
+ context has multi_turn_detection && context.multi_turn_detection == true &&
3749
+ context has jailbreak_deep_context_score && context.jailbreak_deep_context_score >= 60 &&
3750
+ context has jailbreak_pulse_score && context.jailbreak_pulse_score <= 39
3751
+ };
3752
+
3753
+ // ---------------------------------------------------------------------------
3754
+ // Section 2: Blatant trajectory, regardless of the message score
3755
+ // Covers the band between Section 1 and the default single-turn thresholds.
3756
+ // ---------------------------------------------------------------------------
3757
+
3758
+ @id("security.block-trajectory-injection-high")
3759
+ @name("Block high-confidence multi-turn injection")
3760
+ @description("Blocks process_prompt and process_response when threaded state is in use and injection_deep_context_score >= 75.")
3761
+ @severity("high")
3762
+ @tags("category:security,threat:injection,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm01")
3763
+ @reject_message("Prompt blocked: the multi-turn model scored this conversation as a prompt-injection attempt with high confidence.")
3764
+ forbid (
3765
+ principal,
3766
+ action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
3767
+ resource
3768
+ )
3769
+ when {
3770
+ context has multi_turn_detection && context.multi_turn_detection == true &&
3771
+ context has injection_deep_context_score && context.injection_deep_context_score >= 75
3772
+ };
3773
+
3774
+ @id("security.block-trajectory-jailbreak-high")
3775
+ @name("Block high-confidence multi-turn jailbreak")
3776
+ @description("Blocks process_prompt and process_response when threaded state is in use and jailbreak_deep_context_score >= 75.")
3777
+ @severity("high")
3778
+ @tags("category:security,threat:jailbreak,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm02")
3779
+ @reject_message("Prompt blocked: the multi-turn model scored this conversation as a jailbreak attempt with high confidence.")
3780
+ forbid (
3781
+ principal,
3782
+ action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
3783
+ resource
3784
+ )
3785
+ when {
3786
+ context has multi_turn_detection && context.multi_turn_detection == true &&
3787
+ context has jailbreak_deep_context_score && context.jailbreak_deep_context_score >= 75
3788
+ };
3789
+ `;
3790
+ const GUARDRAILS_AGENT_SECURITY_SESSION_RISK_ACCUMULATION_CEDAR = `// =============================================================================
3791
+ // Session Risk Accumulation
3792
+ // =============================================================================
3793
+ // Blocks the privileged action a probing conversation is working toward. A
3794
+ // patient attacker expects some turns to be refused; what they want is one
3795
+ // tool call at the end — send the email, move the money, read the file. So
3796
+ // the useful question at a tool call is not "is this call suspicious?" but
3797
+ // "what has this conversation been doing up to now?"
3798
+ //
3799
+ // Shield accumulates that history on the session and projects it three ways,
3800
+ // because attackers come in three shapes:
3801
+ // - session_max_* — a high-water mark that never decays. "Did this session
3802
+ // EVER cross a line?" Catches the attacker who probes hard, is refused,
3803
+ // goes quiet, then calmly asks for the tool.
3804
+ // - session_cumulative_risk_score — an uncapped running sum. "How much
3805
+ // total pressure has this session applied?" Catches death by a thousand
3806
+ // cuts, where no single turn is alarming.
3807
+ // - session_threat_turns — a count of turns that tripped a detector. "Is
3808
+ // this sustained, or a one-off?" Separates probing from a false positive.
3809
+ //
3810
+ // Requires a stable session_id on every request, prompts AND tool calls. The
3811
+ // tool call must ride the same session as the conversation, or it reads 0.
3812
+ //
3813
+ // Detection layers:
3814
+ // - session (aggregate over detection history, always available)
3815
+ // - tool_validator (tool_is_sensitive, always available)
3816
+ //
3817
+ // Context keys consumed:
3818
+ // - session_max_injection_score, session_max_jailbreak_score: Long (0-100)
3819
+ // - session_cumulative_risk_score: Long — uncapped sum
3820
+ // - session_threat_turns: Long — count
3821
+ // - tool_is_sensitive: Bool
3822
+ //
3823
+ // Compliance:
3824
+ // - OWASP LLM01, OWASP LLM06, OWASP ASI01, OWASP ASI04
3825
+ //
3826
+ // Category: agent-security
3827
+ // Namespace: Guardrails
3828
+ // =============================================================================
3829
+
3830
+ // ---------------------------------------------------------------------------
3831
+ // Section 1: A session that ever crossed the line does not get to act
3832
+ // The conversation is what scored high; the tool call is what gets stopped.
3833
+ // ---------------------------------------------------------------------------
3834
+
3835
+ @id("agent-security.block-tool-after-injection-in-session")
3836
+ @name("Block tools after an injection or jailbreak in the session")
3837
+ @description("Blocks call_tool when session_max_injection_score >= 60 or session_max_jailbreak_score >= 60, because an earlier turn attempted injection or jailbreak.")
3838
+ @severity("critical")
3839
+ @tags("category:agent-security,threat:escalation,detection:aggregate,surface:call-tool,scope:multi-turn,owasp:llm01")
3840
+ @reject_message("Tool execution blocked: an earlier turn in this session attempted prompt injection or jailbreak. Start a new session to use tools.")
3841
+ forbid (
3842
+ principal,
3843
+ action == Guardrails::Action::"call_tool",
3844
+ resource
3845
+ )
3846
+ when {
3847
+ (context has session_max_injection_score && context.session_max_injection_score >= 60) ||
3848
+ (context has session_max_jailbreak_score && context.session_max_jailbreak_score >= 60)
3849
+ };
3850
+
3851
+ // ---------------------------------------------------------------------------
3852
+ // Section 2: Accumulated pressure gates sensitive tools
3853
+ // No single turn set a max, but the session as a whole kept pushing.
3854
+ // ---------------------------------------------------------------------------
3855
+
3856
+ @id("agent-security.block-sensitive-tool-on-session-risk")
3857
+ @name("Block sensitive tools once the session has accumulated risk")
3858
+ @description("Blocks call_tool when session_cumulative_risk_score >= 151 and tool_is_sensitive is true.")
3859
+ @severity("high")
3860
+ @tags("category:agent-security,threat:escalation,detection:aggregate,surface:call-tool,scope:multi-turn,owasp:asi01")
3861
+ @reject_message("Tool execution blocked: this session has accumulated significant risk across earlier turns. Sensitive tools are withheld for the remainder of the session.")
3862
+ forbid (
3863
+ principal,
3864
+ action == Guardrails::Action::"call_tool",
3865
+ resource
3866
+ )
3867
+ when {
3868
+ context has session_cumulative_risk_score &&
3869
+ context.session_cumulative_risk_score >= 151 &&
3870
+ context has tool_is_sensitive && context.tool_is_sensitive == true
3871
+ };
3872
+
3873
+ // ---------------------------------------------------------------------------
3874
+ // Section 3: Sustained probing, independent of any single score
3875
+ // Two is the smallest bar that separates repeated from one-off. Long-running
3876
+ // agent sessions accumulate turns faster and warrant a higher bar.
3877
+ // ---------------------------------------------------------------------------
3878
+
3879
+ @id("agent-security.block-tool-on-repeated-threat-turns")
3880
+ @name("Block tool use in a session with repeated threat turns")
3881
+ @description("Blocks call_tool when session_threat_turns >= 2, because more than one turn in this session tripped a detector.")
3882
+ @severity("high")
3883
+ @tags("category:agent-security,threat:escalation,detection:aggregate,surface:call-tool,scope:multi-turn,owasp:asi04")
3884
+ @reject_message("Tool execution blocked: more than one turn in this session tripped a detector. This is sustained probing, not a one-off false positive.")
3885
+ forbid (
3886
+ principal,
3887
+ action == Guardrails::Action::"call_tool",
3888
+ resource
3889
+ )
3890
+ when {
3891
+ context has session_threat_turns && context.session_threat_turns >= 2
3892
+ };
3893
+ `;
3894
+ const GUARDRAILS_AGENT_IDENTITY_DUAL_ATTRIBUTION_CEDAR = `// =============================================================================
3895
+ // Dual Attribution
3896
+ // =============================================================================
3897
+ // Blocks privileged agent actions that cannot be attributed to a human. "Which
3898
+ // agent did this?" is half an answer; the other half is "on whose behalf?" An
3899
+ // agent is not an accountable party — the person who pointed it at the work
3900
+ // is. Shield projects both sides of that pair, so the requirement can be a
3901
+ // policy rather than a reporting convention nobody enforces.
3902
+ //
3903
+ // The agent comes from the authenticated identity, never from the request
3904
+ // body: agent_id, agent_type, agent_trust_level, agent_framework. The human
3905
+ // comes from the identity claims on the credential, as the \`principal\` record
3906
+ // — its \`act_sub\` field names the party the agent is acting for.
3907
+ //
3908
+ // Deploy in MONITOR mode first. Section 2 blocks unverified agents, and a
3909
+ // service key authenticates as unverified until the agent is registered and
3910
+ // adopted in Studio. Monitor records what each rule would have blocked on
3911
+ // every event without blocking anything; register the agent, then switch to
3912
+ // enforce.
3913
+ //
3914
+ // Detection layers:
3915
+ // - agent identity (authentication layer, always available)
3916
+ // - tool_validator (tool_is_sensitive, tool_category, always available)
3917
+ //
3918
+ // Context keys consumed:
3919
+ // - agent_id, agent_type, agent_trust_level: String
3920
+ // - principal: record — act_sub names the accountable human
3921
+ // - tool_is_sensitive: Bool
3922
+ // - tool_category: String
3923
+ //
3924
+ // Compliance:
3925
+ // - OWASP ASI01, OWASP ASI04
3926
+ //
3927
+ // Category: agent-identity
3928
+ // Namespace: Guardrails
3929
+ // =============================================================================
3930
+
3931
+ // ---------------------------------------------------------------------------
3932
+ // Section 1: No unattributed privileged action
3933
+ // \`unless\` because the rule must fire when attribution is ABSENT, and an
3934
+ // absent field cannot be compared — only tested for.
3935
+ // ---------------------------------------------------------------------------
3936
+
3937
+ @id("agent-identity.require-principal-for-sensitive-tools")
3938
+ @name("Block sensitive agent tools without a principal")
3939
+ @description("Blocks call_tool on a sensitive tool by an agent unless the credential carries a principal whose act_sub names the human the agent acts for.")
3940
+ @severity("critical")
3941
+ @tags("category:agent-identity,detection:rule,surface:call-tool,scope:per-agent,posture:deny-default,owasp:asi01")
3942
+ @reject_message("Tool execution blocked: this agent invoked a sensitive tool with no accountable human attached. Every privileged agent action must name the person it acts for.")
3943
+ forbid (
3944
+ principal,
3945
+ action == Guardrails::Action::"call_tool",
3946
+ resource
3947
+ )
3948
+ when {
3949
+ context has agent_id && context.agent_id != "" &&
3950
+ context has tool_is_sensitive && context.tool_is_sensitive == true
3951
+ }
3952
+ unless {
3953
+ context has principal && context.principal has act_sub
3954
+ };
3955
+
3956
+ // ---------------------------------------------------------------------------
3957
+ // Section 2: Trust level gates the blast radius
3958
+ // Start in monitor: a service key is unverified until the agent is adopted.
3959
+ // ---------------------------------------------------------------------------
3960
+
3961
+ @id("agent-identity.block-unverified-agent-sensitive-tools")
3962
+ @name("Block unverified agents from sensitive tools")
3963
+ @description("Blocks call_tool when agent_trust_level is unverified and the tool is sensitive or dangerous.")
3964
+ @severity("critical")
3965
+ @tags("category:agent-identity,detection:rule,surface:call-tool,scope:per-agent,posture:deny-default,owasp:asi01")
3966
+ @reject_message("Tool execution blocked: unverified agents may not call sensitive or dangerous tools. Register and adopt the agent to raise its trust level.")
3967
+ forbid (
3968
+ principal,
3969
+ action == Guardrails::Action::"call_tool",
3970
+ resource
3971
+ )
3972
+ when {
3973
+ context has agent_trust_level && context.agent_trust_level == "unverified" &&
3974
+ (
3975
+ (context has tool_is_sensitive && context.tool_is_sensitive == true) ||
3976
+ (context has tool_category && context.tool_category == "dangerous")
3977
+ )
3978
+ };
3979
+
3980
+ // ---------------------------------------------------------------------------
3981
+ // Section 3: Autonomous agents get a higher bar
3982
+ // Nobody watches an autonomous agent in real time, so a sensitive tool call
3983
+ // from one requires first-party trust rather than merely "not unverified".
3984
+ // ---------------------------------------------------------------------------
3985
+
3986
+ @id("agent-identity.restrict-autonomous-agent-sensitive-tools")
3987
+ @name("Block non-first-party autonomous agent tool use")
3988
+ @description("Blocks call_tool on a sensitive tool when agent_type is autonomous unless agent_trust_level is first_party.")
3989
+ @severity("high")
3990
+ @tags("category:agent-identity,threat:escalation,detection:rule,surface:call-tool,scope:per-agent,owasp:asi04")
3991
+ @reject_message("Tool execution blocked: autonomous agents must be first-party to call sensitive tools. No human is in the loop to catch a mistake.")
3992
+ forbid (
3993
+ principal,
3994
+ action == Guardrails::Action::"call_tool",
3995
+ resource
3996
+ )
3997
+ when {
3998
+ context has agent_type && context.agent_type == "autonomous" &&
3999
+ context has tool_is_sensitive && context.tool_is_sensitive == true
4000
+ }
4001
+ unless {
4002
+ context has agent_trust_level && context.agent_trust_level == "first_party"
4003
+ };
4004
+ `;
3474
4005
  // =============================================================================
3475
4006
  // CATEGORIES
3476
4007
  // =============================================================================
@@ -3810,6 +4341,69 @@ export const GUARDRAILS_TEMPLATES = [
3810
4341
  severity: 'high',
3811
4342
  tags: ['category:agent-security', 'surface:call-tool', 'aarm:r3', 'posture:deny-default'],
3812
4343
  },
4344
+ {
4345
+ id: 'privacy.output-protection',
4346
+ name: 'Output Protection — Data Leakage',
4347
+ description: 'Block PII, secrets and bulk disclosure in the model\'s own responses. Scoped to the response direction only, so prompts are unaffected.',
4348
+ category: 'privacy',
4349
+ cedarText: GUARDRAILS_PRIVACY_OUTPUT_PROTECTION_CEDAR,
4350
+ severity: 'critical',
4351
+ tags: ['category:privacy', 'threat:data-leak', 'surface:process-response', 'detection:rule'],
4352
+ },
4353
+ {
4354
+ id: 'trust-safety.output-protection',
4355
+ name: 'Output Protection — Harmful Content',
4356
+ description: 'Hold the model\'s own responses to a safety standard: toxicity, explicit content, and injection payloads carried back to the caller. Response direction only.',
4357
+ category: 'trust-safety',
4358
+ cedarText: GUARDRAILS_TRUST_SAFETY_OUTPUT_PROTECTION_CEDAR,
4359
+ severity: 'critical',
4360
+ tags: ['category:trust-safety', 'threat:harmful', 'surface:process-response', 'detection:ml'],
4361
+ },
4362
+ {
4363
+ id: 'security.output-block-injection-carried-back',
4364
+ name: 'Output Protection — Injection Carried Back',
4365
+ description: 'Block responses that themselves score as an injection payload — content a caller, downstream agent or renderer may execute. Response direction only.',
4366
+ category: 'security',
4367
+ cedarText: GUARDRAILS_SECURITY_OUTPUT_BLOCK_INJECTION_CARRIED_BACK_CEDAR,
4368
+ severity: 'high',
4369
+ tags: ['category:security', 'threat:injection', 'surface:process-response', 'detection:ml'],
4370
+ },
4371
+ {
4372
+ id: 'data-protection.output-block-secrets',
4373
+ name: 'Output Protection — Credentials',
4374
+ description: 'Block responses whose own content contains credentials, API keys or tokens — the canonical way a leaked secret reaches a caller. Response direction only.',
4375
+ category: 'data-protection',
4376
+ cedarText: GUARDRAILS_DATA_PROTECTION_OUTPUT_BLOCK_SECRETS_CEDAR,
4377
+ severity: 'critical',
4378
+ tags: ['category:data-protection', 'threat:secrets', 'surface:process-response', 'detection:rule'],
4379
+ },
4380
+ {
4381
+ id: 'security.multi-turn-trajectory',
4382
+ name: 'Multi-Turn Trajectory Escalation',
4383
+ description: 'Block a conversation whose trajectory is an attack even when no single message is: fires on the gap between the multi-turn model and the single-turn classifier.',
4384
+ category: 'security',
4385
+ cedarText: GUARDRAILS_SECURITY_MULTI_TURN_TRAJECTORY_CEDAR,
4386
+ severity: 'critical',
4387
+ tags: ['category:security', 'threat:injection', 'threat:jailbreak', 'detection:ml', 'scope:multi-turn', 'owasp:llm01', 'owasp:llm02'],
4388
+ },
4389
+ {
4390
+ id: 'agent-security.session-risk-accumulation',
4391
+ name: 'Session Risk Accumulation',
4392
+ description: 'Block the privileged action a probing conversation is working toward, using the session\'s high-water mark, cumulative risk, and count of threat turns.',
4393
+ category: 'agent-security',
4394
+ cedarText: GUARDRAILS_AGENT_SECURITY_SESSION_RISK_ACCUMULATION_CEDAR,
4395
+ severity: 'critical',
4396
+ tags: ['category:agent-security', 'threat:escalation', 'detection:aggregate', 'surface:call-tool', 'scope:multi-turn', 'owasp:asi01', 'owasp:asi04'],
4397
+ },
4398
+ {
4399
+ id: 'agent-identity.dual-attribution',
4400
+ name: 'Dual Attribution',
4401
+ description: 'Block privileged agent actions that cannot be attributed to a human. Deploy in monitor mode first; a service key is unverified until the agent is adopted.',
4402
+ category: 'agent-identity',
4403
+ cedarText: GUARDRAILS_AGENT_IDENTITY_DUAL_ATTRIBUTION_CEDAR,
4404
+ severity: 'critical',
4405
+ tags: ['category:agent-identity', 'detection:rule', 'surface:call-tool', 'scope:per-agent', 'posture:deny-default', 'owasp:asi01'],
4406
+ },
3813
4407
  ];
3814
4408
  // =============================================================================
3815
4409
  // TEMPLATES METADATA
@@ -3877,7 +4471,10 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
3877
4471
  "category": "organization",
3878
4472
  "file": "defaults/baseline.cedar",
3879
4473
  "severity": "low",
3880
- "tags": ["category:organization", "posture:permit-default"],
4474
+ "tags": [
4475
+ "category:organization",
4476
+ "posture:permit-default"
4477
+ ],
3881
4478
  "is_active": true
3882
4479
  }
3883
4480
  ],
@@ -3889,7 +4486,10 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
3889
4486
  "category": "organization",
3890
4487
  "file": "defaults/baseline.cedar",
3891
4488
  "severity": "low",
3892
- "tags": ["category:organization", "posture:permit-default"],
4489
+ "tags": [
4490
+ "category:organization",
4491
+ "posture:permit-default"
4492
+ ],
3893
4493
  "auto_deploy": true
3894
4494
  },
3895
4495
  {
@@ -3899,7 +4499,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
3899
4499
  "category": "data-protection",
3900
4500
  "file": "defaults/secrets.cedar",
3901
4501
  "severity": "critical",
3902
- "tags": ["category:data-protection", "threat:secrets", "owasp:llm06"]
4502
+ "tags": [
4503
+ "category:data-protection",
4504
+ "threat:secrets",
4505
+ "owasp:llm06"
4506
+ ]
3903
4507
  },
3904
4508
  {
3905
4509
  "id": "security.injection",
@@ -4043,7 +4647,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4043
4647
  "category": "agent-identity",
4044
4648
  "file": "defaults/agent_identity.cedar",
4045
4649
  "severity": "critical",
4046
- "tags": ["category:agent-identity", "scope:per-agent", "owasp:llm01"]
4650
+ "tags": [
4651
+ "category:agent-identity",
4652
+ "scope:per-agent",
4653
+ "owasp:llm01"
4654
+ ]
4047
4655
  },
4048
4656
  {
4049
4657
  "id": "tools.mcp-tool-permissions",
@@ -4052,7 +4660,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4052
4660
  "category": "tools",
4053
4661
  "file": "mcp_tool_permissions.cedar",
4054
4662
  "severity": "critical",
4055
- "tags": ["category:tools", "threat:supply-chain", "posture:deny-default"]
4663
+ "tags": [
4664
+ "category:tools",
4665
+ "threat:supply-chain",
4666
+ "posture:deny-default"
4667
+ ]
4056
4668
  },
4057
4669
  {
4058
4670
  "id": "tools.mcp-server-allowlist",
@@ -4102,7 +4714,10 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4102
4714
  "category": "data-protection",
4103
4715
  "file": "profiles/code_agent/security.cedar",
4104
4716
  "severity": "critical",
4105
- "tags": ["category:data-protection", "threat:secrets"]
4717
+ "tags": [
4718
+ "category:data-protection",
4719
+ "threat:secrets"
4720
+ ]
4106
4721
  },
4107
4722
  {
4108
4723
  "id": "security.code-agent-encoding",
@@ -4124,7 +4739,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4124
4739
  "category": "security",
4125
4740
  "file": "profiles/code_agent/path_security.cedar",
4126
4741
  "severity": "critical",
4127
- "tags": ["category:security", "threat:secrets", "threat:path-traversal"]
4742
+ "tags": [
4743
+ "category:security",
4744
+ "threat:secrets",
4745
+ "threat:path-traversal"
4746
+ ]
4128
4747
  },
4129
4748
  {
4130
4749
  "id": "agent-security.code-agent",
@@ -4163,7 +4782,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4163
4782
  "category": "data-protection",
4164
4783
  "file": "profiles/data_pipeline/data_protection.cedar",
4165
4784
  "severity": "critical",
4166
- "tags": ["category:data-protection", "threat:secrets", "owasp:llm06"]
4785
+ "tags": [
4786
+ "category:data-protection",
4787
+ "threat:secrets",
4788
+ "owasp:llm06"
4789
+ ]
4167
4790
  },
4168
4791
  {
4169
4792
  "id": "security.data-pipeline-block-injection",
@@ -4172,7 +4795,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4172
4795
  "category": "security",
4173
4796
  "file": "profiles/data_pipeline/security.cedar",
4174
4797
  "severity": "high",
4175
- "tags": ["category:security", "threat:injection", "owasp:llm01"]
4798
+ "tags": [
4799
+ "category:security",
4800
+ "threat:injection",
4801
+ "owasp:llm01"
4802
+ ]
4176
4803
  },
4177
4804
  {
4178
4805
  "id": "agent-security.data-pipeline",
@@ -4181,7 +4808,10 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4181
4808
  "category": "agent-security",
4182
4809
  "file": "profiles/data_pipeline/agentic_security.cedar",
4183
4810
  "severity": "critical",
4184
- "tags": ["category:agent-security", "threat:exfiltration"]
4811
+ "tags": [
4812
+ "category:agent-security",
4813
+ "threat:exfiltration"
4814
+ ]
4185
4815
  },
4186
4816
  {
4187
4817
  "id": "agent-identity.multi-agent-trust",
@@ -4290,7 +4920,11 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4290
4920
  "category": "data-protection",
4291
4921
  "file": "profiles/advanced_detection/secrets.cedar",
4292
4922
  "severity": "critical",
4293
- "tags": ["category:data-protection", "threat:secrets", "owasp:llm06"]
4923
+ "tags": [
4924
+ "category:data-protection",
4925
+ "threat:secrets",
4926
+ "owasp:llm06"
4927
+ ]
4294
4928
  },
4295
4929
  {
4296
4930
  "id": "privacy.advanced-pii",
@@ -4320,6 +4954,112 @@ export const GUARDRAILS_TEMPLATES_JSON = `{
4320
4954
  "aarm:r3",
4321
4955
  "posture:deny-default"
4322
4956
  ]
4957
+ },
4958
+ {
4959
+ "id": "privacy.output-protection",
4960
+ "name": "Output Protection — Data Leakage",
4961
+ "description": "Block PII, secrets and bulk disclosure in the model's own responses. Scoped to the response direction only, so prompts are unaffected.",
4962
+ "category": "privacy",
4963
+ "file": "profiles/output_protection/data_leakage.cedar",
4964
+ "severity": "critical",
4965
+ "tags": [
4966
+ "category:privacy",
4967
+ "threat:data-leak",
4968
+ "surface:process-response",
4969
+ "detection:rule"
4970
+ ]
4971
+ },
4972
+ {
4973
+ "id": "trust-safety.output-protection",
4974
+ "name": "Output Protection — Harmful Content",
4975
+ "description": "Hold the model's own responses to a safety standard: toxicity, explicit content, and injection payloads carried back to the caller. Response direction only.",
4976
+ "category": "trust-safety",
4977
+ "file": "profiles/output_protection/harmful_content.cedar",
4978
+ "severity": "critical",
4979
+ "tags": [
4980
+ "category:trust-safety",
4981
+ "threat:harmful",
4982
+ "surface:process-response",
4983
+ "detection:ml"
4984
+ ]
4985
+ },
4986
+ {
4987
+ "id": "security.output-block-injection-carried-back",
4988
+ "name": "Output Protection — Injection Carried Back",
4989
+ "description": "Block responses that themselves score as an injection payload — content a caller, downstream agent or renderer may execute. Response direction only.",
4990
+ "category": "security",
4991
+ "file": "profiles/output_protection/injection_carried_back.cedar",
4992
+ "severity": "high",
4993
+ "tags": [
4994
+ "category:security",
4995
+ "threat:injection",
4996
+ "surface:process-response",
4997
+ "detection:ml"
4998
+ ]
4999
+ },
5000
+ {
5001
+ "id": "data-protection.output-block-secrets",
5002
+ "name": "Output Protection — Credentials",
5003
+ "description": "Block responses whose own content contains credentials, API keys or tokens — the canonical way a leaked secret reaches a caller. Response direction only.",
5004
+ "category": "data-protection",
5005
+ "file": "profiles/output_protection/credentials.cedar",
5006
+ "severity": "critical",
5007
+ "tags": [
5008
+ "category:data-protection",
5009
+ "threat:secrets",
5010
+ "surface:process-response",
5011
+ "detection:rule"
5012
+ ]
5013
+ },
5014
+ {
5015
+ "id": "security.multi-turn-trajectory",
5016
+ "name": "Multi-Turn Trajectory Escalation",
5017
+ "description": "Block a conversation whose trajectory is an attack even when no single message is: fires on the gap between the multi-turn model and the single-turn classifier.",
5018
+ "category": "security",
5019
+ "file": "multi_turn_trajectory.cedar",
5020
+ "severity": "critical",
5021
+ "tags": [
5022
+ "category:security",
5023
+ "threat:injection",
5024
+ "threat:jailbreak",
5025
+ "detection:ml",
5026
+ "scope:multi-turn",
5027
+ "owasp:llm01",
5028
+ "owasp:llm02"
5029
+ ]
5030
+ },
5031
+ {
5032
+ "id": "agent-security.session-risk-accumulation",
5033
+ "name": "Session Risk Accumulation",
5034
+ "description": "Block the privileged action a probing conversation is working toward, using the session's high-water mark, cumulative risk, and count of threat turns.",
5035
+ "category": "agent-security",
5036
+ "file": "session_risk_accumulation.cedar",
5037
+ "severity": "critical",
5038
+ "tags": [
5039
+ "category:agent-security",
5040
+ "threat:escalation",
5041
+ "detection:aggregate",
5042
+ "surface:call-tool",
5043
+ "scope:multi-turn",
5044
+ "owasp:asi01",
5045
+ "owasp:asi04"
5046
+ ]
5047
+ },
5048
+ {
5049
+ "id": "agent-identity.dual-attribution",
5050
+ "name": "Dual Attribution",
5051
+ "description": "Block privileged agent actions that cannot be attributed to a human. Deploy in monitor mode first; a service key is unverified until the agent is adopted.",
5052
+ "category": "agent-identity",
5053
+ "file": "dual_attribution.cedar",
5054
+ "severity": "critical",
5055
+ "tags": [
5056
+ "category:agent-identity",
5057
+ "detection:rule",
5058
+ "surface:call-tool",
5059
+ "scope:per-agent",
5060
+ "posture:deny-default",
5061
+ "owasp:asi01"
5062
+ ]
4323
5063
  }
4324
5064
  ]
4325
5065
  }