asf-tooling commented on issue #1208:
URL:
https://github.com/apache/tooling-trusted-releases/issues/1208#issuecomment-4409685879
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `new_feature` • **Classification:** `actionable` •
**Confidence:** `medium`
**Application domain(s):** `voting`, `release_lifecycle`
### Summary
Issue #1208 requests a new security-specific behavior for the TRUSTED vote
mode. When a release is flagged as a security release, trusted voting should
only allow votes through ATR (no email-based voting), with no email receipts
sent to public mailing lists. The three modes would be: MANUAL, EMAIL, and
TRUSTED (which behaves as hybrid/#1205 for normal releases, and as
security-only for security releases). This supersedes #939. The current
codebase already has the TRUSTED vote mode with ballot recording, but always
sends email receipts and allows email thread participation alongside ATR
ballots.
### Where this lives in the code today
#### `atr/storage/writers/vote.py` — `FoundationCommitter.cast_trusted`
(lines 65-74)
_needs modification_
This method always creates a MESSAGE_SEND task for the email receipt. For
security releases, the receipt email should not be sent to a public mailing
list.
```python
async def cast_trusted( # noqa: C901
self,
project_key: safe.ProjectKey,
version_key: safe.VersionKey,
choice: sql.VoteChoice,
comment: str,
fullname: str,
expected_vote_seq: int | None = None,
expected_vote_mode: sql.VoteMode | None = None,
) -> tuple[list[str], str]:
```
#### `atr/storage/writers/vote.py` — `CommitteeParticipant.send_user_vote`
(lines 229-236)
_needs modification_
This email-based voting path should be entirely blocked for security
releases since they must only accept votes through ATR.
```python
async def send_user_vote(
self,
release: sql.Release,
vote: str,
comment: str,
fullname: str,
is_binding: bool = False,
) -> tuple[list[str], str]:
```
#### `atr/get/vote.py` — `_render_section_vote` (lines 579-600)
_needs modification_
For security releases, unauthenticated users should NOT see any email-based
voting option, and the UI should indicate votes are only accepted through ATR.
```python
async def _render_section_vote(
page: htm.Block,
release: sql.Release,
session: web.Committer | None,
user_category: UserCategory,
archive_url: str | None,
latest_vote_task: sql.Task | None,
) -> None:
page.h2("#vote")["3. Cast your vote"]
if release.committee is None:
raise ValueError("Release has no committee")
if release.effective_vote_mode == sql.VoteMode.MANUAL:
_render_vote_manual(page)
return
vote_recipient = _vote_recipient(release, latest_vote_task)
if user_category == UserCategory.UNAUTHENTICATED:
_render_vote_unauthenticated(page, release, archive_url,
vote_recipient)
else:
await _render_vote_authenticated(page, release, session,
archive_url, vote_recipient, latest_vote_task)
```
### Where new code would go
- `atr/models/sql.py` — after Release model fields
A field like `is_security_release: bool` on the Release model would flag
which releases require security vote mode. This determines whether TRUSTED mode
behaves as hybrid or security-only.
- `atr/get/vote.py` — after _render_trusted_vote_authenticated
A new function like `_render_security_vote_authenticated` to render the
security-mode-specific voting UI (no email thread references, explicit ATR-only
messaging).
### Proposed approach
The implementation should add an `is_security_release` boolean field to the
Release model in `atr/models/sql.py`. The `effective_vote_mode` property (or a
new property like `is_security_vote`) should expose whether the current vote is
in security mode. The key behavioral changes are:
1. **Vote casting** (`atr/storage/writers/vote.py`): In `cast_trusted`, when
the release is a security release, skip creating the MESSAGE_SEND task (no
email receipt to the public list). The ballot is still recorded in the DB.
Return an empty `email_to` list.
2. **Block email voting** (`atr/post/vote.py`): After the TRUSTED branch,
the EMAIL branch (lines 86-98) should reject votes for security releases with
an error message.
3. **Vote initiation** (`atr/get/voting.py`, `atr/post/voting.py`): For
security releases, only allow `frozenset({sql.VoteMode.TRUSTED})` as allowed
vote modes. The vote email is still sent to announce the vote, but the email
should indicate that voting is only accepted through ATR.
4. **Vote resolution** (`atr/get/resolve.py`): For security releases, skip
email thread tabulation entirely and rely solely on ATR ballot records for
determining the outcome.
5. **UI** (`atr/get/vote.py`): For security releases, render a distinct
voting UI that makes clear votes are only accepted through ATR, with no email
thread participation option for unauthenticated users.
### Suggested patches
#### `atr/storage/writers/vote.py`
Skip sending email receipt for security releases while still recording the
ballot
````diff
--- a/atr/storage/writers/vote.py
+++ b/atr/storage/writers/vote.py
@@ -144,6 +144,10 @@ class FoundationCommitter(GeneralPublic):
potency_label=potency_label,
)
+ # For security releases, do not send email receipts to the
mailing list
+ is_security_vote = getattr(release, 'is_security_release',
False)
+ # TODO: confirm the field name for security release flag on
Release model
+
previous_ballot_query = (
sqlmodel.select(sql.BallotPaper)
.where(sql.BallotPaper.release_key == release.key)
@@ -154,20 +158,23 @@ class FoundationCommitter(GeneralPublic):
)
previous_ballot = (await
self.__data.execute(previous_ballot_query)).scalar_one_or_none()
receipt_message_id = mail.message_id_create()
- task = sql.Task(
- status=sql.TaskStatus.QUEUED,
- task_type=sql.TaskType.MESSAGE_SEND,
- task_args=args.Send(
- email_sender=email_sender,
- email_to=email_to,
- subject=subject,
- body=body_text,
- in_reply_to=start_mid,
- email_cc=email_cc,
- email_bcc=email_bcc,
- message_id=receipt_message_id,
- footer_category=mail.MailFooterCategory.USER,
- ).as_task_args(),
- asf_uid=self.__asf_uid,
- project_key=release.project.key,
- version_key=release.version,
- )
+ task = None
+ if not is_security_vote:
+ task = sql.Task(
+ status=sql.TaskStatus.QUEUED,
+ task_type=sql.TaskType.MESSAGE_SEND,
+ task_args=args.Send(
+ email_sender=email_sender,
+ email_to=email_to,
+ subject=subject,
+ body=body_text,
+ in_reply_to=start_mid,
+ email_cc=email_cc,
+ email_bcc=email_bcc,
+ message_id=receipt_message_id,
+ footer_category=mail.MailFooterCategory.USER,
+ ).as_task_args(),
+ asf_uid=self.__asf_uid,
+ project_key=release.project.key,
+ version_key=release.version,
+ )
ballot = sql.BallotPaper(
release_key=release.key,
vote_seq=release.current_vote_seq,
@@ -180,7 +187,10 @@ class FoundationCommitter(GeneralPublic):
revision_number_at_cast=release.latest_revision_number,
receipt_message_id=receipt_message_id,
)
- self.__data.add_all([task, ballot])
+ if task is not None:
+ self.__data.add_all([task, ballot])
+ else:
+ self.__data.add(ballot)
await self.__data.flush()
await self.__data.commit()
except Exception:
@@ -199,7 +209,7 @@ class FoundationCommitter(GeneralPublic):
replaced_ballot_id=previous_ballot.id if (previous_ballot is
not None) else None,
)
- return [email_to], ""
+ return ([email_to] if not is_security_vote else []), ""
````
#### `atr/post/vote.py`
Block email-based voting for security releases
````diff
--- a/atr/post/vote.py
+++ b/atr/post/vote.py
@@ -84,6 +84,11 @@ async def selected_post( # noqa: C901
await quart.flash(success_message, "success")
return await session.redirect(get.vote.selected,
project_key=str(project_key), version_key=str(version_key))
+ # Block email voting for security releases
+ if getattr(release, 'is_security_release', False):
+ await quart.flash("Security releases only accept votes through
ATR.", "error")
+ return await session.redirect(get.vote.selected,
project_key=str(project_key), version_key=str(version_key))
+
if release.current_vote_seq != cast_vote_form.vote_seq:
await quart.flash("The vote form is stale, please refresh and try
again.", "error")
return await session.redirect(get.vote.selected,
project_key=str(project_key), version_key=str(version_key))
````
### Open questions
- How is a release flagged as a 'security release'? Is it a boolean field on
the Release model, a property of the ReleasePolicy, or determined by a
tag/label during composition?
- Should the vote announcement email still be sent to the mailing list for
security releases (to announce the vote is happening), or should the entire
vote be private?
- For security releases, should there be any form of receipt at all (e.g., a
private confirmation to the voter's @apache.org email)?
- Should the effective_vote_mode property be aware of security release
status, or should a separate property (e.g., is_security_vote) control the
behavior difference?
- How does this interact with podling releases? Can podlings have security
releases?
- The issue references #1205 (hybrid mode) - is that already
merged/implemented, or does this depend on it?
### Files examined
- `atr/post/vote.py`
- `atr/storage/writers/vote.py`
- `atr/get/vote.py`
- `atr/get/voting.py`
- `atr/post/voting.py`
- `atr/tabulate.py`
- `atr/get/resolve.py`
- `atr/post/resolve.py`
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]