Skip to content

πŸ›πŸ§‘β€πŸ’» - SDD catalog discovery silently skips unregistered specification symlinksΒ #804

Closed

Description

@efraespada

Is there an existing issue for this?

Which actions are affected?

Issue, Pull Request, Commits

Which platforms are affected?

macOS reproduced; filesystem Dirent handling is platform independent where symlinks are supported.

Description

The standalone SDK's discovery walk silently omits symlink entries under specs/. A regular unregistered Markdown SDD is rejected, but an unregistered Markdown symlink and a symlinked directory containing an SDD are both ignored. validateCatalog returns no errors and the actual standalone validation entrypoint prints PASS.

Expected: explicit unsupported-path/registration failure for these entries, consistently with the existing rejection of a registered symlink. This is a bounded catalog completeness/path-policy defect. It does not claim that discovery follows external links, reads secrets, executes symlinked code or overwrites targets.

Reproducing the issue

Use unmodified sources at develop ab5f7d7, src/infrastructure/specification_catalog_validator.cjs (Git blob 5caad166b383120e9c155c459f4e57019ab05297), with its original path-policy and publication dependencies (blobs 33c5478cf95323900af5030e4e8ac715e5a9f04b and 3391c7b331aa108acd21b44ec39ac0c2823946f4). No production source is changed.

Save this as repro.cjs and run node repro.cjs /absolute/path/to/copilot/src/infrastructure/specification_catalog_validator.cjs. All files and symlink targets are owned disposable fixtures and are removed in finally.

const fs = require('node:fs');
const path = require('node:path');
const assert = require('node:assert/strict');
const sdk = require(process.argv[2]);
const scratch = fs.mkdtempSync('/private/tmp/copilot-catalog-discovery-');
const root = path.join(scratch, 'repo');
const catalog = { version: 1, capabilities: [{
  id: 'example', title: 'Example', status: 'proposed', scope: 'Discovery fixture',
  owner: 'Maintainers', lastVerified: '2026-10-30', specs: ['specs/example.md'],
  workflows: [], entrypoints: ['source/entry.py'], code: ['source/entry.py'],
  tests: ['checks/example.py'], documentation: ['docs/guide.md'],
}] };
function write(relative, text = 'fixture\n') {
  const target = path.join(root, relative);
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.writeFileSync(target, text);
}
try {
  for (const p of ['specs/example.md', 'source/entry.py', 'checks/example.py', 'docs/guide.md']) write(p);
  write('specs/catalog.json', JSON.stringify(catalog));
  assert.deepEqual(sdk.validateCatalog(root, catalog), []);
  sdk.main(['--write'], root);
  write('specs/unregistered.md');
  const regularErrors = sdk.validateCatalog(root, catalog);
  assert.ok(regularErrors.length > 0);
  fs.unlinkSync(path.join(root, 'specs/unregistered.md'));
  const outside = path.join(scratch, 'owned-outside');
  fs.mkdirSync(outside);
  fs.writeFileSync(path.join(outside, 'hidden.md'), '# Unregistered specification\n');
  fs.symlinkSync(path.join(outside, 'hidden.md'), path.join(root, 'specs/alias.md'));
  const fileErrors = sdk.validateCatalog(root, catalog);
  const registered = structuredClone(catalog);
  registered.capabilities[0].specs.push('specs/alias.md');
  const registeredErrors = sdk.validateCatalog(root, registered);
  assert.ok(registeredErrors.length > 0);
  fs.symlinkSync(outside, path.join(root, 'specs/alias-directory'), 'dir');
  const combinedErrors = sdk.validateCatalog(root, catalog);
  const discovered = sdk.discoverSpecificationFiles(root);
  const savedExit = process.exitCode;
  sdk.main([], root);
  const validationExit = process.exitCode || 0;
  process.exitCode = savedExit;
  console.log(JSON.stringify({
    source: 'vypdev/copilot develop ab5f7d78e2012e999bdf5a50903e879b20738e08',
    regularUnregisteredErrors: regularErrors,
    unregisteredFileSymlinkErrors: fileErrors,
    unregisteredFileAndDirectorySymlinkErrors: combinedErrors,
    registeredSymlinkErrors: registeredErrors,
    discovered, standaloneValidationExit: validationExit,
  }, null, 2));
} finally {
  fs.rmSync(scratch, { recursive: true, force: true });
}

copilot Version

Unreleased develop ab5f7d7, reproduced 2026-10-30 with Node 24 on macOS ARM64. Published v3 remains f5e5a4c / v3.10.6; this is not a claimed regression in a later published release. GeckoCV's copied SDK at 972023c has the same discovery omission, identified during review of vypdev/geckocv#32.

Relevant Log Output

specification catalog generation: PASS (1 capabilities)
specification catalog validation: PASS (1 capabilities)
regularUnregisteredErrors: ["specs/unregistered.md is not registered in the specification catalog."]
unregisteredFileSymlinkErrors: []
unregisteredFileAndDirectorySymlinkErrors: []
registeredSymlinkErrors: ["capabilities[0].specs[1] does not resolve to an existing file inside the repository without symlinks: specs/alias.md."]
discovered: ["specs/example.md"]
standaloneValidationExit: 0

Additional context and comments

Please define explicit fail-closed discovery handling for unsupported specification file/directory symlinks and add positive/negative tests, preserving containment and intentional non-spec exclusions. Do not follow symlinks to solve the omission. The normal unregistered-file rejection and registered-symlink rejection are independent controls already executed above.

Please handle the correction through the Copilot team's normal workflow. This reporter has not started an upstream implementation branch, changed the consumer SDK, reduced GeckoCV's 100-SDD scope or published/deployed anything. The separate read-only catalog output access finding in the older copied SDK should be addressed by adopting the already corrected upstream dependency set; it is not duplicated here.

Activity

  1. added
    bugSomething isn't working
    bugfixSomething isn't working
    priority: highCritical issue, needs immediate attention.
    on Oct 30, 2026
  2. added theissue type on Oct 30, 2026
  3. moved this to Todo in vypdevon Oct 30, 2026
  4. changed the title [-]SDD catalog discovery silently skips unregistered specification symlinks[/-] [+]πŸ› - SDD catalog discovery silently skips unregistered specification symlinks[/+] on Oct 30, 2026
  5. vypbot commented on Oct 30, 2026

    @vypbot
    Contributor

    Hi! I’m @vypbot, the Copilot assistant for this repository.

    I can answer questions, explain the codebase, propose implementation and test plans, review issues and pull requests for potential bugs or security problems, and help authorized maintainers apply changes.

    Try /copilot help to see the available commands, or mention @vypbot with your question.

  6. efraespada commented on Oct 30, 2026

    @efraespada
    MemberAuthor

    Consumer verification update at Copilot SDK pin 071044e96e25d5b7866b2ff6ccbd11a53d12645f: unregistered file, directory and dangling specification links still disappear from direct SDK discovery. The consumer's three actual-package CLI regressions reproduce the omission (126 controls pass); this is the existing #804 condition, not evidence of a newer-release regression.

    GeckoCV PR #32, source b00c85689e32456cedaa1d0d9697027e6d7d1e76, now has a pre-SDK specification-tree check. Its validation and generation commands reject those static links with no PASS, no rewritten catalog/registry and untouched targets. All SDK source/license bytes match the pinned upstream Git blobs; the consumer mitigation does not fix or close direct upstream discovery.

    Native verification 41096290736 passes all 129 metadata/architecture tests on a fresh GitHub-hosted runner. All 100 SDDs remain; no product scope or genuine gate is reduced. Exact SDK provenance, real CLI regression sources and the scoped audit are published in the PR. Upstream #804 remains the SDK owner's work; no upstream branch, implementation or release was created by this consumer update.

  7. moved this from Todo to In Progress in vypdevon Oct 30, 2026
  8. added
    branchedIndicates that a branch has been created for this issue.
    on Oct 30, 2026
  9. 1 remaining item

  10. added
    state:workingWork can proceed on the verified branch or without a branch.
    on Oct 30, 2026
  11. vypbot commented on Oct 30, 2026

    @vypbot
    Contributor

    Implementation plan

    Current status: Ready to start. No action is required from maintainers before implementation.

    1. Confirm the owning contract and SDK scope
      • Review the cataloged specification and the standalone validator’s discovery and path-policy code.
      • Keep discovery non-following and preserve containment rules and intentional exclusions.
    2. Make unsupported symlinks fail closed
      • Update discovery to report symlinked Markdown files and directories under `specs/` as validation errors.
      • Ensure traversal never follows symlinks, including links that point inside the repository.
    3. Add discovery regression coverage
      • Test rejection of unregistered file and directory symlinks, plus the existing registered-symlink failure.
      • Test regular unregistered Markdown rejection, valid discovery, exclusions, and that symlink targets are not traversed.
    4. Update contract evidence and documentation
      • Document the unsupported-path behavior in the owning specification or relevant documentation.
      • Update catalog evidence if the changed implementation or tests require it; regenerate `specs/CATALOG.md` when catalog metadata changes.
    5. Verify the standalone validation path
      • Run the focused tests and the repository specification validation command.
      • Confirm standalone validation exits unsuccessfully for both symlink cases.

    Acceptance: Automated tests demonstrate that unregistered Markdown file symlinks and symlinked directories under `specs/` produce validation errors without following their targets, while intentional exclusions and regular-file discovery behavior remain intact.

    Need something else? Mention the bot with a question or use /copilot help.

  12. added
    size: LIndicates a large-sized task or issue that requires significant effort and time.
    on Oct 30, 2026
  13. vypbot commented on Oct 30, 2026

    @vypbot
    Contributor

    Issue closed β€” historical progress

    Current status: closed as completed.

    The last estimate is retained below as history. Closure does not certify a new percentage, tests, or branch alignment.

    Action required: none for this closed issue. If work continues, reopen the issue; a fresh assessment can update this same card.

    Last assessment before closure (historical)

    Progress: 85% β€” in progress

    > Current status: [R1] Expected: explicit unsupported-path/registration failure for these entries, consistently with the existing rejection of a registered symlink. This is a bounded catalog completeness/path-policy defect. It does not claim that discovery follows external links, reads secrets, executes symlinked code or overwrites targets. β€” The discovery scan detects symbolic links under specs/ and reports them as unsupported without following their targets, including links encountered under nested directories.
    [R2] Please define explicit fail-closed discovery handling for unsupported specification file/directory symlinks and add positive/negative tests, preserving containment and intentional non-spec exclusions. Do not follow symlinks to solve the omission. The normal unregistered-file rejection and registered-symlink rejection are independent controls already executed above. β€” The patch adds tests for file and directory links, target non-traversal, ordinary-file exclusions, and regular discovery behavior. Test definitions are present, but no execution is independently verified.
    [R3] "acceptance": "Automated tests demonstrate that unregistered Markdown file symlinks and symlinked directories under specs/ produce validation errors without following their targets, while intentional exclusions and regular-file discovery behavior remain intact." β€” The added tests exercise generation and validation rejection paths, sentinel preservation, and the copied standalon
    [untrusted content truncated]

    Next: [R2] Please define explicit fail-closed discovery handling for unsupported specification file/directory symlinks and add positive/negative tests, preserving containment and intentional non-spec exclusions. Do not follow symlinks to solve the omission. The normal unregistered-file rejection and registered-symlink rejection are independent controls already executed above. β€” Independent execution evidence is needed to confirm the added positive and negative tests pass.
    [R3] "acceptance": "Automated tests demonstrate that unregistered Markdown file symlinks and symlinked directories under specs/ produce validation errors without following their targets, while intentional exclusions and regular-file discovery behavior remain intact." β€” Independent execution evidence is needed to confirm both entrypoints reject unsupported links without following targets and preserve existing output.

  14. vypbot commented on Oct 30, 2026

    @vypbot
    Contributor

    Issue closed: synchronization recommendation retired

    No branch synchronization is required for this closed issue.

    The previous recommendation is historical. Issue closure does not certify that the branches are synchronized.

  15. moved this from In Progress to Done in vypdevon Oct 30, 2026
  16. added
    100%Progress: 100%
    and removed
    in-progressStart work on an admitted issue.
    state:workingWork can proceed on the verified branch or without a branch.
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

100%Progress: 100%branchedIndicates that a branch has been created for this issue.bugSomething isn't workingbugfixSomething isn't workingpriority: highCritical issue, needs immediate attention.size: LIndicates a large-sized task or issue that requires significant effort and time.

Type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions