Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-changed-content.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ jobs:
env:
CHANGED_FILES: ${{ steps.changed_files.outputs.filtered_changed_files }}
DELETED_FILES: ${{ steps.changed_files.outputs.filtered_deleted_files }}
run: npm test -- src/content-render/tests/render-changed-and-deleted-files.js
run: npm test -- src/content-render/tests/render-changed-and-deleted-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ To use a specific action version, users can configure their {% data variables.pr

### Using tags for release management

{% ifversion immutable-releases-preview %}
{% ifversion fpt or ghec %}
> [!NOTE] If you have enabled immutable releases to help prevent supply chain attacks and accidental changes to your releases, instead see [AUTOTITLE](/actions/how-tos/create-and-publish-actions/using-immutable-releases-and-tags-to-manage-your-actions-releases).
{% endif %}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ To support the developer process in the next section, add two {% data variables.
1. Add a workflow that triggers when a commit is pushed to a feature branch or to `main` or when a pull request is created. Configure the workflow to run your unit and integration tests. For an example, see [this workflow](https://github.com/actions/javascript-action/blob/main/.github/workflows/ci.yml).
1. Add a workflow that triggers when a release is published or edited. Configure the workflow to ensure semantic tags are in place. You can use an action like [JasonEtco/build-and-tag-action](https://github.com/JasonEtco/build-and-tag-action) to compile and bundle the JavaScript and metadata file and force push semantic major, minor, and patch tags. For more information about semantic tags, see [About semantic versioning](https://docs.npmjs.com/about-semantic-versioning).

{% ifversion immutable-releases-preview %}
{% ifversion fpt or ghec %}
> [!NOTE]
> If you enable immutable releases for your repository, you cannot use this action to force push tags tied to releases on {% data variables.product.github %}. To learn how to manage your releases with immutable releases, see [AUTOTITLE](/actions/how-tos/create-and-publish-actions/using-immutable-releases-and-tags-to-manage-your-actions-releases).
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ title: Using immutable releases and tags to manage your action's releases
shortTitle: Use immutable releases
intro: 'Learn how you can use a combination of immutable releases on {% data variables.product.github %} and Git tags to manage your action''s releases.'
versions:
feature: immutable-releases-preview
fpt: '*'
ghec: '*'
topics:
- Actions
- Code Security
- Vulnerabilities
- Dependencies
---

{% data reusables.releases.immutable-releases-preview-note %}

If you enable immutable releases on your action's repository, you can manage your action's releases as follows:

1. To start the release cycle, develop and validate a potential release for your action on a release branch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ How exactly you sign your build will depend on what sort of code you're writing,

For more information, see [AUTOTITLE](/actions/security-guides/encrypted-secrets){% ifversion fpt or ghec %}, [AUTOTITLE](/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect),{% endif %} and [AUTOTITLE](/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners).

{% ifversion immutable-releases-preview %}
{% ifversion fpt or ghec %}

## Use immutable releases

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,43 @@
title: Immutable releases
intro: 'Learn about immutable releases and how they can help you maintain the integrity of your software supply chain.'
versions:
feature: immutable-releases-preview
fpt: '*'
ghec: '*'
type: overview
topics:
- Code Security
- Vulnerabilities
- Dependencies
---

{% data reusables.releases.immutable-releases-preview-note %}
**Immutable releases** are releases where the assets and associated Git tag cannot be changed after publication. The use of this type of release increases security by blocking supply chain attacks. Attackers cannot:
* Inject vulnerabilities or malware into current project releases.
* Make changes to assets and tags that may break developer workflows.

**Immutable releases** are releases where the assets and associated Git tag cannot be changed after publication. They increase security by blocking:
* Supply chain attacks where attackers inject vulnerabilities or malware into current project releases
* Accidental changes to assets and tags that may break developer workflows
## What immutable releases protect

When you enable immutable releases, the following protections are enforced:

* **Git tags cannot be moved or deleted**: Once an immutable release is published, its associated Git tag is locked to a specific commit and cannot be changed or removed.
* **Release assets cannot be modified or deleted**: All files attached to the release (such as binaries and archives) are protected from modification or deletion.

Additionally, creating an immutable release automatically generates a **release attestation**, which is a cryptographically verifiable record of a release containing the release tag, commit SHA, and release assets. Consumers can use this attestation to make sure the releases and artifacts they are using exactly match the published {% data variables.product.github %} releases.

> [!NOTE]
> Immutable releases include protection against repository resurrection attacks. Even if you delete a repository and create a new one with the same name, you cannot reuse tags that were associated with immutable releases in the original repository.

If a release is immutable, you will see "{% octicon "lock" aria-hidden="true" %} Immutable" below the title on the release page.

## Best practices for publishing immutable releases

We recommend you use the following workflow for publishing an immutable release.

1. Create the release as a draft.
1. Attach all associated assets to the draft release.
1. Publish the draft release.

This ensures that all assets are in place before the release becomes immutable, preventing the need to work around immutability restrictions.

## Next steps

To learn how to enable immutable releases for your repository or organization, see [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/preventing-changes-to-your-releases).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ title: Preventing changes to your releases
shortTitle: Prevent release changes
intro: 'You can enforce immutable releases for a repository or organization to prevent potential vulnerabilities.'
versions:
feature: immutable-releases-preview
fpt: '*'
ghec: '*'
type: overview
topics:
- Code Security
- Vulnerabilities
- Dependencies
---

{% data reusables.releases.immutable-releases-preview-note %}

## Enforcing immutable releases for your repository

{% data reusables.repositories.navigate-to-repo %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ title: Verifying the integrity of a release
shortTitle: Verify release integrity
intro: 'You can avoid tampering and accidental changes by ensuring the releases you use have not been modified after publication.'
versions:
feature: immutable-releases-preview
fpt: '*'
ghec: '*'
type: overview
topics:
- Code Security
Expand All @@ -12,8 +13,6 @@ topics:
defaultTool: cli
---

{% data reusables.releases.immutable-releases-preview-note %}

{% cli %}

## Prerequisites
Expand Down
2 changes: 1 addition & 1 deletion content/copilot/concepts/auto-model-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contentType: concepts

Experience less rate limiting and reduce the mental load of choosing a model by letting {% data variables.copilot.copilot_auto_model_selection %} automatically choose the best available model.

In {% data variables.product.prodname_vscode_shortname %}, {% data variables.copilot.copilot_auto_model_selection %} chooses from {% data variables.copilot.copilot_gpt_41 %}, {% data variables.copilot.copilot_gpt_5_mini %}, {% data variables.copilot.copilot_gpt_5 %}, {% data variables.copilot.copilot_claude_sonnet_35 %}, and {% data variables.copilot.copilot_claude_sonnet_40 %}, based on availability and to help reduce rate limiting. Included models may change over time.
In {% data variables.product.prodname_vscode_shortname %}, {% data variables.copilot.copilot_auto_model_selection %} chooses from {% data variables.copilot.copilot_gpt_41 %}, {% data variables.copilot.copilot_gpt_5_mini %}, {% data variables.copilot.copilot_gpt_5 %}, {% data variables.copilot.copilot_claude_sonnet_35 %}, and {% data variables.copilot.copilot_claude_sonnet_45 %}, based on availability and to help reduce rate limiting. Included models may change over time.

Automatically selected models **won't** include these models:
* Models with premium request multipliers greater than one. See [AUTOTITLE](/copilot/reference/ai-models/supported-models#model-multipliers).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ You can choose whether {% data variables.large_files.product_name_long %} ({% da

## Creating a release

{% ifversion fpt or ghec %}

> [!TIP]
> If you have enabled immutable releases for your repository, it's recommended to create releases as drafts first, attach all assets, and then publish. This ensures all assets are in place before the release becomes immutable. For more information, see [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases).

{% endif %}

{% webui %}

{% data reusables.repositories.navigate-to-repo %}
Expand Down Expand Up @@ -67,9 +74,11 @@ If you @mention any {% data variables.product.github %} users in the notes, the

## Editing a release

{% ifversion immutable-releases-preview %}
{% ifversion fpt or ghec %}

> [!NOTE]
> If you have enabled immutable releases for your repository, you can only edit the title and release notes after a release is published. See [AUTOTITLE](/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases).

{% endif %}

{% webui %}
Expand Down
2 changes: 1 addition & 1 deletion data/reusables/releases/finish-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
1. Optionally, if {% data variables.product.prodname_discussions %} is enabled for the repository, create a discussion for the release.
* Select **Create a discussion for this release**.
* Select the **Category** dropdown menu, then click a category for the release discussion.
1. If you're ready to publicize your release, click **Publish release**. To work on the release later, click **Save draft**.
1. If you're ready to publicize your release, click **Publish release**. To work on the release later, click **Save draft**.{% ifversion fpt or ghec %} If you have enabled immutable releases for the repository, creating a draft first allows you to attach all assets before the release becomes immutable.{% endif %}

{%- ifversion fpt or ghec %}
You can then view your published or draft releases in the releases feed for your repository. For more information, see [AUTOTITLE](/repositories/releasing-projects-on-github/viewing-your-repositorys-releases-and-tags).
Expand Down
2 changes: 0 additions & 2 deletions data/reusables/releases/immutable-releases-preview-note.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
// @ts-ignore - no types available for markdownlint-rule-helpers
import { addError } from 'markdownlint-rule-helpers'
import { getFrontmatter } from '@/content-linter/lib/helpers/utils'

import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'

interface PropertyLimits {
max: number
recommended: number
required?: boolean
}

interface ContentRules {
title: PropertyLimits
shortTitle: PropertyLimits
intro: PropertyLimits
requiredProperties: string[]
}

type ContentType = 'category' | 'mapTopic' | 'article' | null

// Strip liquid tags from text for character counting purposes
function stripLiquidTags(text) {
if (typeof text !== 'string') return text
function stripLiquidTags(text: unknown): string {
if (typeof text !== 'string') return text as string
// Remove both {% %} and {{ }} liquid tags
return text.replace(/\{%.*?%\}/g, '').replace(/\{\{.*?\}\}/g, '')
}
Expand All @@ -13,15 +31,15 @@ export const frontmatterValidation = {
description:
'Frontmatter properties must meet character limits and required property requirements',
tags: ['frontmatter', 'character-limits', 'required-properties'],
function: (params, onError) => {
const fm = getFrontmatter(params.lines)
function: (params: RuleParams, onError: RuleErrorCallback) => {
const fm = getFrontmatter(params.lines as string[])
if (!fm) return

// Detect content type based on frontmatter properties and file path
const contentType = detectContentType(fm, params.name)

// Define character limits and requirements for different content types
const contentRules = {
const contentRules: Record<string, ContentRules> = {
category: {
title: { max: 70, recommended: 67 },
shortTitle: { max: 30, recommended: 27 },
Expand All @@ -42,7 +60,7 @@ export const frontmatterValidation = {
},
}

const rules = contentRules[contentType]
const rules = contentType ? contentRules[contentType] : null
if (!rules) return

// Check required properties
Expand All @@ -61,14 +79,21 @@ export const frontmatterValidation = {

// Check title length
if (fm.title) {
validatePropertyLength(onError, params.lines, 'title', fm.title, rules.title, 'Title')
validatePropertyLength(
onError,
params.lines as string[],
'title',
fm.title,
rules.title,
'Title',
)
}

// Check shortTitle length
if (fm.shortTitle) {
validatePropertyLength(
onError,
params.lines,
params.lines as string[],
'shortTitle',
fm.shortTitle,
rules.shortTitle,
Expand All @@ -78,17 +103,24 @@ export const frontmatterValidation = {

// Check intro length if it exists
if (fm.intro && rules.intro) {
validatePropertyLength(onError, params.lines, 'intro', fm.intro, rules.intro, 'Intro')
validatePropertyLength(
onError,
params.lines as string[],
'intro',
fm.intro,
rules.intro,
'Intro',
)
}

// Cross-property validation: if title is longer than shortTitle limit, shortTitle must exist
const strippedTitle = stripLiquidTags(fm.title)
if (fm.title && strippedTitle.length > rules.shortTitle.max && !fm.shortTitle) {
const titleLine = findPropertyLine(params.lines, 'title')
if (fm.title && (strippedTitle as string).length > rules.shortTitle.max && !fm.shortTitle) {
const titleLine = findPropertyLine(params.lines as string[], 'title')
addError(
onError,
titleLine,
`Title is ${strippedTitle.length} characters, which exceeds the shortTitle limit of ${rules.shortTitle.max} characters. A shortTitle must be provided.`,
`Title is ${(strippedTitle as string).length} characters, which exceeds the shortTitle limit of ${rules.shortTitle.max} characters. A shortTitle must be provided.`,
fm.title,
null,
null,
Expand All @@ -98,10 +130,10 @@ export const frontmatterValidation = {
// Special validation for articles: should have at least one topic
if (contentType === 'article' && fm.topics) {
if (!Array.isArray(fm.topics)) {
const topicsLine = findPropertyLine(params.lines, 'topics')
const topicsLine = findPropertyLine(params.lines as string[], 'topics')
addError(onError, topicsLine, 'Topics must be an array', String(fm.topics), null, null)
} else if (fm.topics.length === 0) {
const topicsLine = findPropertyLine(params.lines, 'topics')
const topicsLine = findPropertyLine(params.lines as string[], 'topics')
addError(
onError,
topicsLine,
Expand All @@ -115,9 +147,16 @@ export const frontmatterValidation = {
},
}

function validatePropertyLength(onError, lines, propertyName, propertyValue, limits, displayName) {
function validatePropertyLength(
onError: RuleErrorCallback,
lines: string[],
propertyName: string,
propertyValue: string,
limits: PropertyLimits,
displayName: string,
): void {
const strippedValue = stripLiquidTags(propertyValue)
const propertyLength = strippedValue.length
const propertyLength = (strippedValue as string).length
const propertyLine = findPropertyLine(lines, propertyName)

// Only report the most severe error - maximum takes precedence over recommended
Expand All @@ -142,7 +181,8 @@ function validatePropertyLength(onError, lines, propertyName, propertyValue, lim
}
}

function detectContentType(frontmatter, filePath) {
// frontmatter object structure varies based on YAML content, using any for flexibility
function detectContentType(frontmatter: any, filePath: string): ContentType {
// Only apply validation to markdown files
if (!filePath || !filePath.endsWith('.md')) {
return null
Expand All @@ -168,7 +208,7 @@ function detectContentType(frontmatter, filePath) {
return 'article'
}

function findPropertyLine(lines, property) {
function findPropertyLine(lines: string[], property: string): number {
const line = lines.find((line) => line.trim().startsWith(`${property}:`))
return line ? lines.indexOf(line) + 1 : 1
}
Loading
Loading