Repository navigation
Description
Problem
Security and scope questions are left to the model. In the plan reviews and fixes of 2026-11-02 they were the findings that most needed a person:
- docs(openspec): plan #2184 Use ipaddress for single-address egress checks #3156: loosening a guard (
192.0.0.9), a Python floor,netaddras a new dependency. - docs(openspec): plan #2651 Add loki.url config key for k8s logs #3151: egress and trust of a stored URL.
- docs(openspec): plan #2750 Refuse Go, Yarn and uv env keys #3165: ambient
UV_TOOL_DIRandUV_ENV_FILEreach an isolated child; a behavior change for anyone who exports them. - docs(openspec): plan #3218 dry-run config unset writes nothing #3234: credential redaction put in the wrong layer, with no security note.
- docs(openspec): plan #3060 plan model tier ladder for retries #3225:
SECURITY.mditem 12 not considered. - docs(openspec): plan #2877 Read check runs only in the implement watch #3168: an App token permission that no test can check.
Proposed change
A deterministic trigger and a check:
- The trigger fires when the plan's
Changesor tasks name a path or a word from a list:SECURITY.md,k8s/RBAC and NetworkPolicy files, an admission policy,sanitizer,credential,keyring,egress, a new host, a new dependency inpyproject.toml, or a loosened guard. - When it fires,
design.mdmust hold a## Securitypart that is not blank and answers: what crosses a trust boundary, what a person must confirm, and what test fails if the rule breaks. check_proposalrefuses a plan with the trigger and no such part, and the message names the matched path or word.- The prompt names the part and the trigger.
Acceptance criteria
- A test shows that a plan that changes
SECURITY.mdand has no## Securitypart is refused, and that the same plan with the part passes. - A test shows that a plan with no trigger needs no part.
- The trigger list is a named constant with a docstring, and a test fails when a path of the list does not exist.
- The test(roadmap): check the planner prompt against its rules #3210 prompt-vs-rules test passes.
Proposed design
Problem statement
Roadmap plans can touch security-sensitive paths, credentials, egress, RBAC, or a new dependency and still pass without a security review. The change adds a deterministic trigger and requires non-blank security text in the plan, which the stage job renders as a ## Security section in design.md. The check names the matched path or word, and the stage prompt states the rule.
Acceptance criteria
Requirement: Security trigger refuses a plan without a security section
The proposal check SHALL return a problem that names the matched path when a group's changes names SECURITY.md and security is blank.
Scenario: Plan changes SECURITY.md without security text
- WHEN
check_proposalreceives a proposal whose groupchangesholdsSECURITY.mdand whosesecurityfield is empty - THEN
check_proposalreturns a problem that namesSECURITY.mdand asks for a non-blank## Securitysection.
Requirement: Security trigger accepts a plan with a security section
The proposal check SHALL return no security problem when a trigger matches and security holds non-blank text.
Scenario: Plan changes SECURITY.md with security text
- WHEN the same proposal has
securityset to non-blank text - THEN
check_proposalreturns no security problem.
Requirement: A plan without a trigger needs no security section
The proposal check SHALL NOT require security when no trigger matches.
Scenario: Plan changes only README.md
- WHEN
check_proposalreceives a proposal whosewhat_changes, groupchanges, and task texts name no trigger - THEN
check_proposalreturns no security problem.
Requirement: Security trigger paths are a named constant
The security trigger paths MUST be the named constant SECURITY_TRIGGER_PATHS with a docstring.
Scenario: The trigger path constant is defined
- WHEN a test imports
SECURITY_TRIGGER_PATHSfromsrc/devops_cli/roadmap/stage_change.py - THEN the constant has a docstring and holds
SECURITY.md,pyproject.tomlandk8s/.
Requirement: Every trigger path exists
The test suite MUST fail when a path in SECURITY_TRIGGER_PATHS does not exist in the repository.
Scenario: A trigger path is missing
- WHEN the test resolves each entry of
SECURITY_TRIGGER_PATHSfrom the repository root - THEN the test fails if an entry does not exist.
Requirement: Security trigger refuses a word trigger without a security section
The proposal check SHALL refuse a plan that names a security trigger word and has no non-blank security text.
Scenario: Plan names credential without a security section
- WHEN
check_proposalreceives a proposal whose task text namescredentialand whosesecurityfield is empty - THEN
check_proposalreturns a problem that namescredential.
Requirement: The design file renders the security text
The design file renderer SHALL write a non-blank security field as a ## Security section.
Scenario: A plan with security text is rendered
- WHEN
render_changerenders a proposal whosesecurityfield holds non-blank text - THEN
design.mdholds a## Securitysection with that text.
Requirement: The stage reply holds a security field
StageProposal MUST have a security field that accepts the plan's security text.
Scenario: A reply with security text validates
- WHEN a stage reply sets
securityto non-blank text - THEN
StageProposalaccepts the reply and keeps the text insecurity.
Requirement: The prompt names the security part and trigger
The stage prompt MUST name the ## Security part and the security trigger.
Scenario: The stage prompt is read
- WHEN a test reads the stage prompt file
roadmap_stage_change.md - THEN the prompt text names the
## Securitypart and the security trigger.
Requirement: The prompt and rules agree
The #3210 prompt-vs-rules test MUST pass with the new prompt text.
Scenario: Prompt-rules test runs
- WHEN
tests/roadmap/test_roadmap_stage_prompt_rules.pyruns - THEN the test passes.
Existing Tool Considered
No alternatives check has run. No open, maintained tool covers a repository-specific plan gate. The change builds the gate in src/devops_cli/roadmap/stage_change.py. Estimated cost: 60,000 tokens and 2 days to build, keep and fix.
Key questions
- Why did the stage job return the item, and how does the plan hold the
## Securitypart?: The stage job found thatdesign.mdis generated by_design_filefromStageProposal, andStageProposalhas no field for security text. The revised plan adds an optionalsecurityfield toStageProposaland renders it in_design_fileas a## Securitysection. (best practice) [sources: src/devops_cli/roadmap/stage_change.py]- Chosen: Add a
securityfield and render it. The model supplies the text in the stage reply, and the job renders the fixed section. - Rejected: Require the model to edit
design.md. The stage job rendersdesign.mdfromStageProposalaftercheck_proposal, so no model edit exists. - Rejected: Reuse
decisionsentries. The issue requires a fixed## Securitysection that the checker can find.
- Chosen: Add a
- Where should the security trigger and its check live?: Add
SECURITY_TRIGGER_PATHS,SECURITY_TRIGGER_WORDS, and the trigger check tosrc/devops_cli/roadmap/stage_change.py. Call the check fromcheck_proposalin the same module. (best practice) [sources: src/devops_cli/roadmap/stage_change.py, tests/roadmap/test_roadmap_stage_plan.py]- Chosen:
stage_change.py.check_proposallives here and already collects proposal problems. - Rejected:
stage_specs.py. This module checks spec deltas, not the proposal text. - Rejected: A new module. A new module adds an import and a test surface for one small check.
- Chosen:
- How should the trigger match the plan text?: Match path entries against each path in
group.changesandgroup.creates, and against each repository path inwhat_changes. Match word entries case-insensitively inwhat_changesand in the task texts. Treatk8s/as a prefix and the other paths as exact matches. (best practice) [sources: src/devops_cli/roadmap/stage_change.py, tests/roadmap/test_roadmap_stage_plan.py]- Chosen: Scan
what_changes, group paths and task texts. The issue says the plan's Changes or tasks can name the trigger. - Rejected: Scan group
changesonly. A word inwhat_changesor a task can name a credential or egress without a path. - Rejected: Scan the whole proposal. The problem and feasibility text can use a trigger word without changing a guarded path.
- Chosen: Scan
- What must the
## Securitysection contain?: The checker requires a non-blanksecurityfield. The prompt asks the plan to answer what crosses a trust boundary, what a person must confirm, and what test fails if the rule breaks. The checker does not parse the prose. (best practice) [sources: src/devops_cli/roadmap/stage_change.py, tests/roadmap/test_roadmap_stage_plan.py]- Chosen: Non-blank text only. This is deterministic and matches the acceptance test.
- Rejected: Three fixed labels. The issue names no labels, and exact labels reject valid prose.
- Rejected: A model review of the section. A model check is not deterministic and the gate must run offline.
- Which paths belong in the trigger path list?: The list holds
SECURITY.md,pyproject.tomland thek8s/directory. A test resolves each entry from the repository root and fails on a missing entry. (best practice) [sources: SECURITY.md, pyproject.toml]- Chosen: Exact files and one directory prefix. The issue names these paths and the
k8s/tree. - Rejected: Glob patterns. A glob can match no file and hide a missing path.
- Rejected: Only paths that exist at review time. The test must fail when a named path disappears.
- Chosen: Exact files and one directory prefix. The issue names these paths and the
- Which words belong in the trigger word list?: The list holds
sanitizer,credential,keyring,egress,admission policy,networkpolicy,rbac,new host,loosen,loosenedandloosening. (best practice) [sources: SECURITY.md, src/devops_cli/security/secrets.py]- Chosen: Case-insensitive word and phrase match. The issue names words and phrases, and case is not meaningful.
- Rejected: Case-sensitive exact match. A plan can write
Credentialand escape the check. - Rejected: Regular expressions. Regular expressions add complexity for a short fixed list.
- What message does the check return when the trigger fires?: It returns one problem that names the first matched path or word and asks the author to add a non-blank
## Securitysection todesign.md. (best practice) [sources: src/devops_cli/roadmap/stage_change.py, src/devops_cli/lang/en/messages.py]- Chosen: Name the matched term. The issue requires the message to name the matched path or word.
- Rejected: A generic security message. The author cannot tell which trigger fired.
- Does the repository already have the test(roadmap): check the planner prompt against its rules #3210 prompt-vs-rules test?: Yes.
tests/roadmap/test_roadmap_stage_prompt_rules.pyexists and checks that the stage prompt and the rules agree. [sources: tests/roadmap/test_roadmap_stage_prompt_rules.py, docs/ROADMAP_WORKFLOW.md] - Where do the new tests go?: Add the trigger and section cases to
tests/roadmap/test_roadmap_stage_plan.py. Keep the prompt case intests/roadmap/test_roadmap_stage_prompt_rules.py. (best practice) [sources: tests/roadmap/test_roadmap_stage_plan.py, tests/roadmap/test_roadmap_stage_prompt_rules.py]- Chosen:
test_roadmap_stage_plan.py. This file testscheck_proposaland the plan checks. - Rejected:
test_roadmap_stage_form.py. This file tests the form of the reply, not the security rule. - Rejected: A new test file. The existing plan test file already holds the related cases.
- Chosen:
- Do the trigger paths exist at the staging commit?: Yes.
SECURITY.md,pyproject.tomland thek8s/directory exist in the repository. [sources: SECURITY.md, pyproject.toml]
Fit
Fits in one pull request.
Suspected block
None.
Dependencies
None.
Alternatives check of 2026-11-06: none.
Tool: none found.
Activity
devops-cli-service commentedon Nov 2, 2026
Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.
featprefix of the title gives the type.(roadmap)scope of the title gives the scope.It closes a recurring gap in plan quality but does not fix an active vulnerability.It prevents security-relevant plans from shipping without a security section, reducing review burden.It needs a trigger list, a check in check_proposal, a prompt update, and four acceptance tests.Both add deterministic checks to plan review, but #3201 adds a general plan-check command while the candidate adds a specific security-section requirement with a trigger list.devops-cli-service commentedon Nov 2, 2026
Status set to Ready at 56c702a (see proposed design in issue body).
devops-cli-service commentedon Nov 2, 2026
The stage job returned the item to New. The premise of the issue does not hold at the staging commit, so the job planned no change and opened no pull request.
Evidence:
probe
14 remaining items
docs(openspec): plan #3247 Require a security section for sensitive p…
docs(openspec): plan #3247 Require a security section for sensitive p…
feat(roadmap): require a security section for sensitive plans (#3247)
devops-cli-service commentedon Nov 7, 2026
Released in v0.85.0: https://github.com/dan-petty/devops-cli/releases/tag/v0.85.0
devops-cli-service commentedon Nov 7, 2026
Live since 2026-11-07 23:20 UTC